共用方式為


Tutorial 2: Making Your Model Move Using Input

This article details how to use the XNA Framework Input API to take user input from an Xbox 360 Controller and apply it to the model displayed in Tutorial 1.

Note

This tutorial builds on code you have written during the previous tutorial: Tutorial 1: Displaying a 3D Model on the Screen. Follow the steps in the previous tutorial before starting this tutorial.

  • Step 1: Connect Your Xbox 360 Controller
  • Step 2: Create Variables to Turn and Move the Model
  • Step 3: Take Input from the User
  • Congratulations!
  • Ideas to Expand
  • The Complete Example (contents of Game1.cs)

Step 1: Connect Your Xbox 360 Controller

The first step in this tutorial is to make sure you can provide some input to your game. We'll use the Xbox 360 Controller. Designed for use with both a Windows computer and an Xbox 360 console, the controller features many analog and digital inputs, as well as vibration motors to give feedback to the user.

This tutorial uses only the Xbox 360 Controller, but there are more ways to take input: the XNA Framework has support for keyboard and mouse devices. Mouse devices are supported only on Windows, not on Xbox 360. For more information on the different input types, see Input Overview.

For now, connect your Xbox 360 Controller and get ready to code!

Step 2: Create Variables to Turn and Move the Model

We want our ship to move around on the screen. To do that, we'll need to create some variables to track the position and orientation of our model in the world.

Fortunately, from our last tutorial (Tutorial 1: Displaying a 3D Model on the Screen), we have two variables to do just that: modelPosition, which is a three-dimensional vector, and modelRotation, which is a floating-point value.

Currently, this system allows three degrees of translation (changing position in the world), but only one degree of rotation (changing orientation). For this demonstration, we will use that limitation to simplify our input. In many 3D games, there are three degrees of translation, and three degrees of rotation, but this is a good start.

What we can do right now to make input a little more interesting is add another vector for velocity. By updating the position with the velocity each frame, our 3D model can accelerate and decelerate smoothly. Let's try it.

  • Make sure your project from Tutorial 1: Displaying a 3D Model on the Screen is open. If it isn't, open it by selecting the File menu, clicking Open Project, and browsing to your project.

  • View the code by double-clicking Game1.cs in Solution Explorer.

  • In the code, find the Update method. Modify it to look like this:

    // Set the velocity of the model, applied each frame to the model's position.
    Vector3 modelVelocity = Vector3.Zero;
    
    protected override void Update( GameTime gameTime )
    {
        if (GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.Pressed)
            this.Exit();
    
        // Get some input.
        UpdateInput();
    
        // Add velocity to the current position.
        modelPosition += modelVelocity;
    
        // Bleed off velocity over time.
        modelVelocity *= 0.95f;
    
        base.Update( gameTime );
    }
    

The code you've just added to input runs every frame, and does a few different things. First, it gets rid of the code that automatically rotates the ship; you'll be controlling that using your controller. Next, it calls a method named UpdateInput. That method does not exist yet; you'll have to create it in the next step. Last, it adds the velocity of our model to its position, moving it in the world by its velocity, and decays the velocity so that eventually the model will slow down.

Step 3: Take Input from the User

Now that the model is set up to move with velocity, you now need to provide some logic that will change the velocity based on controller input.

A simple system that we can use is an orientation thrust method: in essence, you can point the front of your model in different directions using your controller's thumbstick, then apply thrust in the direction you are pointing using your controller's trigger. By building up thrust in a direction, the model will begin to move. This is similar to how the ships in the Spacewar Starter Kit move.

We can map our controls to the game this way.

  • Pressing left or right on the left thumbstick will increase or decrease the value of the modelRotation variable by some amount.

  • Pressing on the right trigger will add a vector in the direction of our modelRotation variable to our modelVelocity vector.

  • Pressing the A button will reset the position, velocity, and rotation values of the model to "warp" the ship back to the center of the screen.

    Note

    Both the triggers and the thumbsticks are analog controls, meaning that they can report their movements in varying amounts, rather than just on or off. On the Xbox 360 Controller, the two thumbsticks and two triggers are analog controls, and all other buttons are digital buttons.

Let's code it!

  • Find some empty space in your code below the Update method.

  • Add a new method called protected void UpdateInput().

  • Modify the method to look like this:

    protected void UpdateInput()
    {
        // Get the game pad state.
        GamePadState currentState = GamePad.GetState( PlayerIndex.One );
        if (currentState.IsConnected)
        {
            // Rotate the model using the left thumbstick, and scale it down.
            modelRotation -= currentState.ThumbSticks.Left.X * 0.10f;
    
            // Create some velocity if the right trigger is down.
            Vector3 modelVelocityAdd = Vector3.Zero;
    
            // Find out what direction we should be thrusting, using rotation.
            modelVelocityAdd.X = -(float)Math.Sin( modelRotation );
            modelVelocityAdd.Z = -(float)Math.Cos( modelRotation );
    
            // Now scale our direction by how hard the trigger is down.
            modelVelocityAdd *= currentState.Triggers.Right;
    
            // Finally, add this vector to our velocity.
            modelVelocity += modelVelocityAdd;
    
            GamePad.SetVibration( PlayerIndex.One, currentState.Triggers.Right,
                currentState.Triggers.Right );
    
    
            // In case you get lost, press A to warp back to the center.
            if (currentState.Buttons.A == ButtonState.Pressed)
            {
                modelPosition = Vector3.Zero;
                modelVelocity = Vector3.Zero;
                modelRotation = 0.0f;
            }
        }
    }
    

That method does a lot. Let's take it piece by piece to investigate exactly what you're doing with input and the model.

// Get the game pad state.
GamePadState currentState = GamePad.GetState( PlayerIndex.One );

This call to GetState retrieves a GamePadState object, which contains the information we need about the controller—in this case, thumbstick and trigger positions.

// Rotate the model using the left thumbstick, and scale it down.
modelRotation -= currentState.ThumbSticks.Left.X * 0.10f;

Retrieving the x-axis value of the left thumbstick (left and right movement) returns a value that is added to the modelrotation variable. The value is scaled down so that the rotation isn't too fast.

// Create some velocity if the right trigger is down.
Vector3 modelVelocityAdd = Vector3.Zero;

// Find out what direction we should be thrusting, using rotation.
modelVelocityAdd.X = -(float)Math.Sin( modelRotation );
modelVelocityAdd.Z = -(float)Math.Cos( modelRotation );

// Now scale our direction by how hard the trigger is down.
modelVelocityAdd *= currentState.Triggers.Right;

A little math here helps translate the rotation of the ship into a vector. Taking the sine value of the rotation gives us the proper amount of X (left and right) movement, and the cosine gives us the Z (forward and back) movement. Then, we take the vector and lengthen it by how hard the player is holding down the right trigger.

// Finally, add this vector to our velocity.
modelVelocity += modelVelocityAdd;

Finally, the created vector is added to the current velocity vector to create the final velocity vector that will be applied to move the model around.

GamePad.SetVibration( PlayerIndex.One, currentState.Triggers.Right,
    currentState.Triggers.Right );

We're using the right trigger values to give some feedback to the player with the Xbox 360 Controller vibration motors, using SetVibration . The Xbox 360 Controller has two motors that run at different speeds, so experiment to find the best combination for the action that's happening in the game.

// In case you get lost, press A to warp back to the center.
if (currentState.Buttons.A == ButtonState.Pressed)
{
    modelPosition = Vector3.Zero;
    modelVelocity = Vector3.Zero;
    modelRotation = 0.0f;
}

This little extra will move the model back to its original position and orientation in case it leaves the screen.

Congratulations!

At this point, your ship moves and gives you feedback through your Xbox 360 Controller. The player is in control of the action.

When you're ready, let's add the final element—audio—to get you on your way. Once the player can control the action, and see and hear the results of their actions, you're well on your way to creating a game.

Next...

Tutorial 3: Making Sounds with XNA Game Studio Express and XACT

Ideas to Expand

Want to play around some more with input? Try these ideas.

  • Change the game to view your model from the top, as in a top-down arcade game. (Hint: Play with the cameraPosition vector; note that you can't set it exactly up and down because the camera vector cannot be the same as the "up" vector.)
  • Scale the vibration to occur more powerfully as the ship approaches the viewer. (Hint: Use the distance between modelPosition and cameraPosition.)
  • Try using a keyboard to control the ship; see the Keyboard class. (Hint: You can plug a USB keyboard into your Xbox 360 console.)

The Complete Example (contents of Game1.cs)

#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;


    public Game1()
    {
        graphics = new GraphicsDeviceManager( this );
        content = new ContentManager( Services );
    }

    protected override void Initialize()
    {

        base.Initialize();
    }

    // Specify the 3D model to draw.
    Model myModel;

    // The aspect ratio determines how to scale 3d to 2d projection.
    float aspectRatio;

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            myModel = content.Load<Model>("Content\\Models\\p1_wedge");
        }

        aspectRatio = graphics.GraphicsDevice.Viewport.Width /
            graphics.GraphicsDevice.Viewport.Height;
    }

    protected override void UnloadGraphicsContent( bool unloadAllContent )
    {
        if (unloadAllContent == true)
        {
            content.Unload();
        }
    }


    // Set the velocity of the model, applied each frame to the model's position.
    Vector3 modelVelocity = Vector3.Zero;

    protected override void Update( GameTime gameTime )
    {
        if (GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        // Get some input.
        UpdateInput();

        // Add velocity to the current position.
        modelPosition += modelVelocity;

        // Bleed off velocity over time.
        modelVelocity *= 0.95f;

        base.Update( gameTime );
    }

    protected void UpdateInput()
    {
        // Get the game pad state.
        GamePadState currentState = GamePad.GetState( PlayerIndex.One );
        if (currentState.IsConnected)
        {
            // Rotate the model using the left thumbstick, and scale it down.
            modelRotation -= currentState.ThumbSticks.Left.X * 0.10f;

            // Create some velocity if the right trigger is down.
            Vector3 modelVelocityAdd = Vector3.Zero;

            // Find out what direction we should be thrusting, using rotation.
            modelVelocityAdd.X = -(float)Math.Sin( modelRotation );
            modelVelocityAdd.Z = -(float)Math.Cos( modelRotation );

            // Now scale our direction by how hard the trigger is down.
            modelVelocityAdd *= currentState.Triggers.Right;

            // Finally, add this vector to our velocity.
            modelVelocity += modelVelocityAdd;

            GamePad.SetVibration( PlayerIndex.One, currentState.Triggers.Right,
                currentState.Triggers.Right );


            // In case you get lost, press A to warp back to the center.
            if (currentState.Buttons.A == ButtonState.Pressed)
            {
                modelPosition = Vector3.Zero;
                modelVelocity = Vector3.Zero;
                modelRotation = 0.0f;
            }
        }
    }

    // Set the position of the model in world space, and set the rotation.
    Vector3 modelPosition = Vector3.Zero;
    float modelRotation = 0.0f;

    // Set the position of the Camera in world space, for our view matrix.
    Vector3 cameraPosition = new Vector3( 0.0f, 50.0f, -5000.0f );

    protected override void Draw( GameTime gameTime )
    {
        graphics.GraphicsDevice.Clear( Color.CornflowerBlue );

        // Copy any parent transforms.
        Matrix[] transforms = new Matrix[myModel.Bones.Count];
        myModel.CopyAbsoluteBoneTransformsTo( transforms );

        // Draw the model. A model can have multiple meshes, so loop.
        foreach (ModelMesh mesh in myModel.Meshes)
        {
            // This is where the mesh orientation is set, as well as our camera and projection.
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.EnableDefaultLighting();
                effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateRotationY( modelRotation )
                    * Matrix.CreateTranslation( modelPosition );
                effect.View = Matrix.CreateLookAt( cameraPosition, Vector3.Zero, Vector3.Up );
                effect.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.ToRadians( 45.0f ),
                    aspectRatio, 1.0f, 10000.0f );
            }
            // Draw the mesh, using the effects set above.
            mesh.Draw();
        }

        base.Draw( gameTime );
    }
}