Star Trek -Part II: Adding moving 3D Objects to your XNA Game

Introduction

This is an update of the Star Trek XNA demonstration in which I ported from Visual Studio Express to Visual Studio 2005 and XNA 2.0.  It adds a more realistic looking Klingon ship and a rotating 3D Planet.  In this article we will discuss how to add 3D Objects and Text to your games.

Meshes

The planet consists of a sphere mesh coated with a 2D Texture.  Meshes are a set of vertices in 3D space that describe a 3D Object.  You can create meshes using professional tools such as Maya and 3D Studio.  You can also create meshes with an open source tool such as blender. Blender takes a bit of skill to learn, but if you follow the tutorials, you'll soon be creating some interesting 3D Meshes.  XNA supports the direct-x  format (*.x)  for meshes so you'll need to export your mesh in this format. Once you have a mesh, you can easily read and manipulate it using the objects in XNA.

1.gif

Figure 1 Star Trek

Below is the code to read a Mesh into your game:

Listing 1 - Loading a 3D Mesh

ModelMesh _Sphere;  // this is the mesh object
protected override void LoadGraphicsContent(bool loadAllContent)
{
     if (loadAllContent)
     {
          //  this line loads the Mesh from a   direct x file  (with the .x extension)
          _Sphere = content.Load<Model>((@"Content\Textures\Sphere1")).
          Meshes[0];
      
}
}


Once we have a mesh we need to position the mesh, scale the mesh, and add a texture to the mesh.  All of these actions are accomplished by changing the Effects of the mesh.  Each basic effect of the Effects collection has properties such as world coordinates, lighting, and textures to allow us to manipulate the mesh structure.  Listing 2 shows the method  for positioning our sphere on the screen and adding a bitmap texture to its surface.  All transforms are performed using a 3D Matrix. XNA gives us a Matrix class that makes it simple to create our transforms. The scale matrix is created using the CreateScale method with a single scalar value relative to the game surface.  A value of 1.0 indicates the entire game surface. The scale matrix is then multiplied by the World coordinates of the Effect to get the appropriate scaling of our 3D object.  The sphere is moved around the screen using a translation matrix created from the CreateTranslation method using a 3D offset vector. The translation is performed  in 3D space relative to View coordinates. A coordinate of (1, 0, 0) positions the planet where we want it, to the far right and centered vertically. 

Textures are also assigned to the Effect through the Texture property. The 2D texture that was loaded in the LoadGraphicsContent method is assigned to an effect to give us a texture on the surface of our mesh.

Listing 2 - Changing the Effects of the Mesh

private void SetDefaultMeshEffects(ModelMesh mesh, float scale, Vector3 position, Texture2D texture)
 {
    foreach (BasicEffect effect in mesh.Effects)
     {
        effect.EnableDefaultLighting();  // set default lighting
        effect.World = effect.World * Matrix.CreateScale(scale);     // scale the world coordinates

      effect.Texture = texture;

      effect.View = Matrix.CreateTranslation(position);

effect.TextureEnabled = true;

}

}

Rotating the Planet

The planet is rotated by changing transforming the rotation of the sphere every time it enters the Update method of the Game object. Again, the effect of the planet mesh is multiplied by a transformation matrix to rotate the sphere. In this example we rotate the sphere .02 radians around the X and the Z axises.

Listing 3 - Rotating the Planet Every Time an Update is Called

/// <summary>
///
This is called from the Update Method
/// </summary>
private void RotatePlanet()
{
  // go through each effect in the sphere and rotate it's
  // world coordinates .02 radians around the X and Z
  // Axises
foreach (BasicEffect effect in _Sphere.Effects)
  {
    effect.EnableDefaultLighting();
    effect.World = effect.World * Matrix.CreateRotationX(-.02f) * Matrix.CreateRotationZ(-.02f);
   }

}

Joystick Movement

XNA makes controlling an XBox joystick a snap with its built in GamePad class.  By just reading the state of the pad, you can get button information and thumbstick positions with a few easy to use static methods. The method UpdateFromGamePad shown in listing 4 is called from Update in the game class. In keeping with the keypad control, we decided to use the left thumbstick to move forward and backward and the right thumbstick to rotate clockwise and counterclockwise. What is nice about the joystick classes in XNA is the code is almost self-explanatory. You can get the state of a particular button and see if it was pressed or released, or you can get the state of a thumbstick (Left or Right) and see its X and Y position vector.  In listing 4 we round the left thumbstick's X vector positions in order to determine if we are moving forward or backward.  A value of 1 indicates forward and a value of -1 indicates backward.  We use the same technique for rotation.  We round the right thumbstick's X vector positions in order to determine if we are rotating clockwise or counterclockwise. A value of 1 indicates clockwise and a value of -1 indicates counter clockwise.

Listing 4 - Reading the Joystick State

private void UpdateFromGamePad()
 {

// get the vector position of the left and right thumbstick
   float transx = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.X;
   float transy = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.Y;
   float rotx = GamePad.GetState(PlayerIndex.One).ThumbSticks.Right.X;
   float roty = GamePad.GetState(PlayerIndex.One).ThumbSticks.Right.Y;

   //  test to see if we are rotating with the right thumbstick
   if ( (int)(rotx + .5f) == 1)
               _ship.Update(0, 0, .01f);

    if ((int)(rotx + -.5f) == -1)
             _ship.Update(0, 0, -.01f);

   //  test to see if we are moving with the left thumbstick

    if ((int)(transx + .5f) == 1)
      {
            _ship.Update(1, 0, 0);
      }

     if ((int)(transx + -.5f) == -1)
       {
            _ship.Update(-1, 0, 0);
       }

   // see if we are firing a photon torpedo.  if we are then set the firing state

if (GamePad.GetState(PlayerIndex.One).Buttons.LeftShoulder == ButtonState.Pressed)
 {
    if (_weaponState == WeaponState.IDLE)
      {
         _photon.Position = new Vector2(_ship.Position.X, _ship.Position.Y);
         _photon.Angle = _ship.Angle;
           Sound.Play("photon_torpedo");
     }

     _weaponState = WeaponState.PHOTON_MOVING;

}

}

 

Drawing Fonts

Fonts are handled much in the same way as textures. You add a SpriteFont to the content of your project. You then load the font using the ContentManager. Finally you draw the font in the Draw routine of your game object.

Start by right clicking on your project in solution explorer and choose, Add New Item. Pick the Sprite Font from the Content Dialog shown in figure 2:

2.gif

Figure 2 Adding a new content Item

This will produce an xml file that defines your font. Go into the content file and edit the file for the font parameters needed to customize the appearance of your font. In our example, we changed the font to Arial Italic, size 24 as shown in listing 5.

Listing 5 - Editing the Sprite Font Content File

<?xml version="1.0" encoding="utf-8"?>

<!--

This file contains an xml description of a font, and will be read by the XNA
Framework Content Pipeline. Follow the comments to customize the appearance
of the font in your game, and to change the characters which are available to draw
with.

-->

<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">

<Asset Type="Graphics:FontDescription">

<!--

Modify this string to change the font that will be imported. Redistributable sample
fonts are available at http://go.microsoft.com/fwlink/?LinkId=104778&clcid=0x409.

-->

<FontName>Arial</FontName>

<!--
Size is a float value, measured in points. Modify this value to change
the size of the font.
-->

<Size>24</Size>

<!--
Spacing is a float value, measured in pixels. Modify this value to change
the amount of spacing in between characters.
-->

<Spacing>0</Spacing>

<!--
UseKerning controls the layout of the font. If this value is true, kerning information
will be used when placing characters.
-->

<UseKerning>true</UseKerning>

<!--
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
and "Bold, Italic", and are case sensitive.
-->

<Style>Italic</Style>

<!--
CharacterRegions control what letters are available in the font. Every
character from Start to End will be built and made available for drawing. The
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
character set. The characters are ordered according to the Unicode standard.
See the documentation for more information.
-->

<CharacterRegions>

<CharacterRegion>

<Start>&#32;</Start>

<End>&#126;</End>

</CharacterRegion>

</CharacterRegions>

</Asset>

</XnaContent>

 

Once we have a SpriteFont defined and added to our project, we can load the font.  We simply call Load on the ContentManager object with the path of our font. In our example, we chose to put the font into a Font folder to better organize our project, so the path for the font becomes Content\Fonts\StarTrekFont as shown in listing 6.  You can determine the name of your asset, by clicking on the SpriteFont in your project and looking at the Asset Name. In this case, our Asset is called StarTrekFont.

Listing 6 - Loading the font into the game

SpriteFont _font;

/// <summary>

/// Load your graphics content. If loadAllContent is true, you should
/// load content from both ResourceManagementMode pools. Otherwise, just
/// load ResourceManagementMode.Manual content.
/// </summary>
///
<param name="loadAllContent">Which type of content to load.</param>
protected override void LoadGraphicsContent(bool loadAllContent)
{
  if (loadAllContent)
   {
      _font = content.Load<SpriteFont>(@"Content\Fonts\StarTrekFont");

...

Once the font is loaded, it is ready to draw. We draw the font in the Draw method provided by the Game class. We display the font by calling the DrawString method on the SpriteBatch class.  This method allows us to position and color our font as we render it to the screen:

Listing 7 - Rendering the Font to the screen

/// <summary>
///
This is called when the game should draw itself.
/// </summary>
///
<param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
 {
   graphics.GraphicsDevice.Clear(Color.Black);
   DrawBackground();
  _spriteBatch.Begin();
  _spriteBatch.DrawString(_font, "Star Trek", new Vector2(550, 550), Color.Aquamarine);
  _spriteBatch.End();

...

Conclusion

Adding 3D Objects to your game is a fairly simple process once you are comfortable working in the 3D coordinate system. You can experiment with different textures on the sphere to get different types of planets. Also demonstrated in this article was use of the joystick and drawing of fonts to a game. Stay tuned for our next XNA article for additional game functionality and improvements to Star Trek.


Similar Articles