Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Content Obsolete

This content is no longer current.

Beginning Game Development: Part IV - DirectInput

  This is Part 4 of an introductory series on game programming using the Microsoft .NET Framework and managed DirectX 9.0. This article covers the input device portion of DirectX, called DirectInput.
3Leaf Development

Difficulty: Intermediate
Time Required: 3-6 hours
Cost: Free
Software: Visual Basic or Visual C# Express Editions, DirectX SDK
Hardware: None
Download:
Beginning Game Development Series
  1. Beginning Game Development Part 1 - Introduction
  2. Beginning Game Development Part II - Introduction to DirectX
  3. Beginning Game Development: Part III - DirectX II
  4. Beginning Game Development: Part IV - DirectInput
  5. Beginning Game Development: Part V - Adding Units
  6. Beginning Game Development: Part VI - Lights, Materials and Terrain
  7. Beginning Game Development: Part VII –Terrain and Collision Detection
  8. Beginning Game Development: Part VIII - DirectSound
  9. Beginning Game Development: Part VIII - DirectSound II
  10. Beginning Game Development: Part VIII - DirectSound III

Welcome to the fourth article on beginning game development. In this article we are going to cover the input device portion of DirectX, called DirectInput. Using DirectInput you can control joysticks, a mouse or the keyboard.

Before we start I need to cover a couple of items that were brought to my attention via feedback from the readers (thank you everyone for taking the time to do this) and changes not directly related to the items covered in this article.

Code Cleanup

To make adding the code for this section easier and to make the code more reusable I have moved some of the code from the GameEngine class into separate classes such as Camera. Encapsulating the camera functionality in a separate object makes the code easer to change and to read. The following changes have already been integrated into the code for this article.

  1. I moved the FrameworkTimer class out of the DirectX support code into a class called HiResTimer and removed the using statements for Microsoft.Samples.DirectX.UtilityToolkit. For the VB version I converted the FrameworkTimer class into VB and removed the reference to the DirectX Sample Framework library.
  2. I refactored the device creation code in the constructor of the GameEngine class into the ConfigureDevice method.
  3. I refactored all code that deals with the device in the OnPaint method of the GameEngine class into the Render method.
  4. I refactored the code that sets up the camera in the OnPaint method of the GameEngine class into the Camera class.
  5. I removed the CreateCrossHairVertexArrayTop, CreateCrossHairVertexArrayBottom, CreateTestTriangle, and CreateTestCube methods from the GameEngine Class.
  6. Removed the System.Collections.Generic, System.ComponentModel, System.Data and System.Text using statements from the GameEngine class.
  7. I removed all of the code used for experimenting with the camera settings.

In addition to these general housekeeping changes I added some code that I needed to make the DirectInput portion more interesting.

Skybox

The infinite 3D space we have created has one problem: it's infinite. We have no distinguishing terrain features to orient us and, as such, have no idea in which direction we are facing. We could create a number of complex 3D objects to draw a realistic terrain, but this is very slow and we really do not need 'real' terrain, but only to create the look of real terrain. To create this illusion of seeing a horizon and terrain in the distance, we make use of a technique called the skybox. A skybox creates the effect you get when placing your head into a cardboard box, the inside of which is painted with a landscape: Regardless where you are and where you look you see the inside of the box and nothing else.

In our game we create a cube and display textures (or pictures for us that prefer to use more normal definitions) on the inside walls of the cube. The top would be textured like the sky, the bottom like the ground and the remaining four sides have a texture applied to them that shows a horizon and terrain. The pictures for the four sides are designed in such a manner that they match perfectly at the edges and produce a seamless landscape. These four sides could then represent cardinal directions such as east, west, north and south (if your game is set on Earth).

To achieve the correct effect, we need to ensure that all objects are drawn on top of the skybox. This is done by disabling Z-buffering before drawing the skybox and enabling it after we are done.

Visual C#

_device.RenderState.ZBufferWriteEnable = false; 
// draw the skybox here
_device.RenderState.ZBufferWriteEnable = true;

Visual Basic


_device.RenderState.ZBufferWriteEnable = False
'draw the skybox here
_device.RenderState.ZBufferWriteEnable = True

The next thing we need to ensure is that the player can never actually get close to or beyond the skybox (otherwise they would notice the illusion). We accomplish this by ensuring that the viewpoint is always in the center of the skybox.

First we assign an Identity matrix to the world matrix. Since the vertices of the skybox are in model space (offset around zero), this ensures that they are not translated into world space. Then we set the use the View matrix of the camera to set the View matrix of the skybox.

Visual C#

Matrix worldMatrix = Matrix.Identity;
Matrix viewMatrix = cam.View;

viewMatrix.M41 = 0.0f;
viewMatrix.M42 = 0.0f;
viewMatrix.M43 = 0.0f;

_device.Transform.View = viewMatrix;
_device.Transform.World = worldMatrix;

Visual Basic

Dim worldMatrix As Matrix = Matrix.Identity
Dim viewMatrix As Matrix = cam.View
viewMatrix.M41 = 0.0F
viewMatrix.M42 = 0.0F
viewMatrix.M43 = 0.0F
_device.Transform.View = viewMatrix
_device.Transform.World = worldMatrix

Drawing the actual skybox uses the techniques we have discussed in the previous articles and I am not going to cover them in detail. The basic steps are as follows.

  1. Define a PositionNormalTextured vertex array to hold the data for the four corners of each cube face and texture information.
  2. Load the texture for each cube face from a file.
  3. Setup the Vertex buffer for each face.
  4. On each Render loop readjust the skybox.
  5. On each Render loop disable Z-buffering.
  6. On each Render loop use the Camera object passed in to determine the direction the camera is facing and draw the appropriate face.
  7. On each Render loop turn Z-buffering back on.

If you look at the RenderFace method of the SkyBox class, you will notice that it is identical to the code we used to draw the triangle and cube in the last article.

Visual C#

_device.SetStreamSource ( 0, faceVertexBuffer, 0 );
_device.VertexFormat = CustomVertex.PositionNormalTextured.Format;
_device.SetTexture ( 0, faceTexture );
_device.DrawPrimitives ( PrimitiveType.TriangleStrip, 0, 2 );

Visual Basic

_device.SetStreamSource(0, faceVertexBuffer, 0)
_device.VertexFormat = CustomVertex.PositionNormalTextured.Format
_device.SetTexture(0, faceTexture)
_device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2)
Camera Class

The other major refactoring I did for this article is to move all the camera-related code into the Camera class. The fixed values such as FoV and aspect ratio used in the Perspective matrix are set as internal properties with the appropriate default values. I have also added methods that allow the camera to be moved left or right and up or down, and a method that moves the position of the camera. Why I added these methods will become clear once we integrate user input later in the article.

Now that all of the graphics for the game except the enemy units are done, let's get the tank moving so we can explore our new world.

Controlling Input

Controlling input devices is not nearly as cool as graphics manipulation or artificial intelligence, but without it you couldn't have a game. Reacting to user input allows the user to move around in and manipulate the 3D world we have created.

The DirectInput API allows you to control the mouse, keyboard, joystick, game-pad, or force feedback device. Some games also provide the ability for voice input to control the game, but that is beyond the scope of our game.

Adding DirectInput

The first step when adding input support to an application is to reference the Microsoft.DirectX.DirectInput.dll assembly. The next step is to add the using statement to each class in which you plan to use the DirectInput classes (unless you really like typing).

Detecting Devices

A good API provides a common way to access similar devices. This is true in DirectX where physical devices such as video cards, sound cards, and input devices are abstracted into the Device class. This class shields us as developers from knowing any of the hardware-specific details. In the DirectInput namespace a Device represents any of the potential input devices.

Another familiar construct is the Manager class. We used the Manager class from the Direct3D namespace to get information about the adapters and to retrieve device capabilities for each adapter. In DirectInput the process is very similar.

To get a list of specific devices such as all keyboards connected to the computer, you use the GetDevices method of the Manager class and pass in the DeviceClass or DeviceType and one of the EnumDevicesFlags enumeration values to filter the list further. A computer can have many input devices but as a minimum it should have a keyboard and mouse.

Note: Most of the time you are going to use EnumDevicesFlags.AttachedOnly to get only the attached devices of a particular type.

To make things a little more obscure, a mouse is considered a Pointer type in the DeviceClass enumeration. You can use any combination of to retrieve a custom list of input devices.

Each call to GetDevices returns a DeviceList class. This DeviceList in turn contains a DeviceInstance structure for each device. The DeviceInstance structure contains the information about each device, the most important of which is the InstanceGuid. This is a unique identifier for each device on our system that we need to know so we can communicate with that device.

Note: Just because a device is in the DeviceList does not mean that it is actually attached. You can use the InstanceGuid and the GetDeviceAttached method to determine if the device is attached.

The Manager class exposes a Devices property that contains a DeviceList.

Note that the DeviceInstance structure is not actually a device; there is nothing we can do with it in terms of connecting to it. The only use it has is to provide a GUID which we can then use to actually connect to or acquire that device.

Connecting to a Keyboard

The first device we are going to connect to is the keyboard. This is probably the most common device used to interact with games. Most modern games enable the user to control the game using the keyboard and the mouse, as there are too many actions to perform to use just one of these devices. Depending on your audience, you need to choose the primary input device and decide whether to offer configurable choices for secondary devices. You also need to remember that some computers, such as slate-mode Tablet PCs, have no keyboard, so offering alternate input means broadens your potential audience.

Regardless of what input options you offer, the steps to set up any input device are the same.

  1. Instantiate a Device class passing the type of device.
  2. Set the cooperation level of the device.
  3. Set the format of the data returned for the device.
  4. Acquire the device.
  5. Poll the device.
  6. Read the state data to determine what actions the user did.

Using good OO practices, we first create a keyboard class that will encapsulate all keyboard-specific functionality and provide a single access point for the rest of the game to any keyboard functionality.

The first step is to create a Device object for the keyboard. We do this using the Keyboard value of the SystemGuid enumeration. This lets us connect to the default keyboard. Using the default keyboard is safer than enumerating the devices and picking a specific one, because you don't know beforehand what devices all your users might have.

Visual C#

_device = new Device ( SystemGuid.Keyboard );

Visual Basic

_device = New Device(SystemGuid.Keyboard)

The next step is to set the Cooperative level of the device. The values of the CooperativeLevelFlags enumeration determine how much control we chose to take over the device and how much control we leave to other applications.

The CooperativeLevelFlags enumeration contains five values.

  • Exclusive
  • NonExclusive
  • Foreground
  • Background
  • NoWindowsKey

The Background and Foreground values are mutually exclusive, as are the Exclusive and NonExclusive values. The Foreground option states that we only want data from the device if the window we passed into the SetCooperativeLevel method has the focus. The Background option simply means that we always want data from the device. Exclusive means that we want priority for control of the device while NonExclusive means we don't. Even when using the Exclusive option it is still possible to lose the device, which is why we add the reacquire logic to the Poll method to ensure we can get the device back when we want to read its state. The NoWindowsKey option can be combined with any of the other settings and specifies that we want to ignore the Windows logo key on the keyboard. This setting is important when running in full screen mode, because the Windows key causes the application to loose focus.

For BattleTank 2005 we combine the Background and NonExclusive values to allow other applications maximum control over this device.

We also need to pass in a reference to the window we are using so DirectX knows which window's input we are interested in.

Visual C#

_device.SetCooperativeLevel ( form, CooperativeLevelFlags.Background | 
CooperativeLevelFlags.NonExclusive );

Visual Basic



_device.SetCooperativeLevel(form, CooperativeLevelFlags.Background Or _ 
CooperativeLevelFlags.NonExclusive)

The next step is to determine the data format we expect this device to return. We cover the contents of this data structure a little later in the article.

Visual C#

_device.SetDataFormat ( DeviceDataFormat.Keyboard ); 

Visual Basic

_device.SetDataFormat(DeviceDataFormat.Keyboard)

The last step is to actually acquire the device. You can think of this like opening a communications channel to the device. Since there are lots of things that could go wrong at this point, it is best to wrap the Acquire call into a try/catch block.

Visual C#

try
{
_device.Acquire ( );
}
catch ( DirectXException ex )
{
Console.WriteLine ( ex.Message );
}

Visual Basic

Try
_device.Acquire()
Catch ex As DirectXException
Console.WriteLine(ex.Message)
End Try

After acquiring the device we can read its state, which is a byte array. The first 256 values of this array hold the state of the keyboard; the next 8, the state of the mouse; and the last 32, the state of the joystick. Setting the device data format simply restricts the returned array to the set of values for the specified device. The KeyboardState structure, for example, only contains the first 256 bytes of the raw state.

Visual C#

private KeyboardState _state; 

Visual Basic

Private _state As KeyboardState

To get this state from the device we first call the Poll method. This method communicates with the actual hardware. Then we copy the state into a local data structure (the _state variable).

Since we are going to do this on every frame, I placed this particular sequence of calls into their own public method called Poll. In the game loop we then call _keyboard.Poll() and use the state data structure to see what keys were pressed. This is where we deal with a device which may have been acquired by another application by attempting to reacquire it.

Visual C#

try
{
_device.Poll ( );
_state = _device.GetCurrentKeyboardState ( );
}
catch ( NotAcquiredException )
{
// try to reqcquire the device
try
{
_device.Acquire ( );
}
catch ( InputException iex )
{
Console.WriteLine ( iex.Message );
// could not get the device
}
}
catch ( InputException ex2 )
{
Console.WriteLine ( ex2.Message );
}

Visual Basic

Try
_device.Poll()
_state = _device.GetCurrentKeyboardState
Catch generatedExceptionVariable0 As NotAcquiredException
Try
_device.Acquire()
Catch iex As InputException
Console.WriteLine(iex.Message)
End Try
Catch ex2 As InputException
Console.WriteLine(ex2.Message)
End Try

The only step remaining is to examine the state data returned to see what actions the user performed.

In addition to managing the device state each time we poll the device, we want to make sure to release it when we are finished using it. We accomplish this by calling the Unacquire method of the device in the Dispose method.

Visual C#

if ( _device != null )
_device.Unacquire ( );

Visual Basic

If Not (_device Is Nothing) Then
_device.Unacquire()
End If
Connecting to a Mouse

Connecting to a mouse is almost identical to connecting to a keyboard. The differences are the SystemGuid passed to the Device constructor, the DeviceDataFormat, the data structure used to store the state information, and the method call used to retrieve the device state.

Visual C#

_device = new Device ( SystemGuid.Mouse );
_device.SetDataFormat ( DeviceDataFormat.Mouse );
private MouseState _state;
public void Poll ( )
{
_device.Poll ( );
_state = _device.CurrentMouseState;
}

Visual Basic

_device = New Device(SystemGuid.Mouse)
_device.SetDataFormat(DeviceDataFormat.Mouse)
Private _state As MouseState
Public Sub Poll()
_device.Poll()
_state = _device.CurrentMouseState
End Sub

Using the mouse also provides the ability to define how the X, Y and Z (No, don't pick up your mouse, the Z value is the value for the mouse scroll wheel on certain mice) values are reported on each poll. Setting the AxisModeAbsolute value to true returns the coordinates in screen coordinates, while setting this value to false returns the change in pixels from the previous poll. We set this value as soon as we have acquired the device.

Visual C#

_device.Acquire ( );
_device.Properties.AxisModeAbsolute = false;

Visual Basic

_device.Acquire()
_device.Properties.AxisModeAbsolute = False

Using the false option is valuable if you want to determine the speed in which the user moved the mouse using the time elapsed between polls and the distance traveled in that time frame while the first option is more valuable if the user is selecting something from the screen.

Connecting to a Joystick

Connecting to a joystick follows the same general steps as for the keyboard and mouse, but, since there is no such thing as a default joystick, no corresponding SystemGuid value exists. Instead, we need to use the GetDevices method of the Manager class, passing in GameControl type for the DeviceClass and AttachedOnly for the EnumDevicesFlags to enumerate any attached joysticks. You would probably want to present some type of UI to the user to let them choose from a list of devices found or you could just use the first device as the code snippet does.

Visual C#

DeviceList gameControllerList = 
Manager.GetDevices(
DeviceClass.GameControl,
EnumDevicesFlags.AttachedOnly);

if (gameControllerList.Count > 0)
{
foreach (DeviceInstance deviceInstance in gameControllerList)
{
_device = new Device(deviceInstance.InstanceGuid);
_device.SetCooperativeLevel(form,
CooperativeLevelFlags.Background |
CooperativeLevelFlags.NonExclusive);
break;
}
}

Visual Basic

Dim gameControllerList As DeviceList
gameControllerList = Manager.GetDevices(DeviceClass.GameControl, _
EnumDevicesFlags.AttachedOnly)
If (gameControllerList.Count > 0) Then
Dim deviceInstance As DeviceInstance
For Each deviceInstance In gameControllerList
_device = New Device(deviceInstance.InstanceGuid)
_device.SetCooperativeLevel(Form, CooperativeLevelFlags.Background
Or CooperativeLevelFlags.NonExclusive)
Break()
Next
End If

The next step is to specify a new DeviceDataFormat and new type for the _state data structure. Finally you need to retrieve the joystick state from the device inside the Poll method.

Visual C#

_device.SetDataFormat ( DeviceDataFormat.Joystick );
private JoystickState _state;
public void Poll ( )
{
_device.Poll ( );
_state = _device.CurrentJoystickState;
}

Visual Basic

_device.SetDataFormat(DeviceDataFormat. Joystick)
Private _state As JoystickState
Public Sub Poll()
_device.Poll()
_state = _device.CurrentJoystickState
End Sub

In BattleTank 2005 we are not going to use a joystick, but feel free to add this capability on your own if you have a joystick. If you own a force-feedback device you might also want to experiment with using it in the game.

Now that we have access to the various forms of input devices, and know how to retrieve their state we need to know how to read the various state data structures to determine user actions.

Determining User Actions

Each of the data structures for the state are byte arrays. The KeyboardState is a byte array 256 bytes long. Each byte represents the state of a key on the keyboard and the position in the array matches the value of the Key enumeration. The indexer for the KeyboardState returns a Boolean value for each index checked. For example, to see if the user pressed the Escape key you simply test that the most significant bit is set at that index location (position 1 for Escape) in the array. If this expression returns true then the user pressed the Escape key, otherwise it returns false.

Each poll method may return values for multiple keys such as when the user pressed certain key combinations (Ctrl+Alt+Delete is a common one for me when I program). DirectInput supports a maximum of five key values, but you should be nice to your users and stick to single key actions if possible.

Visual C#

_state[Key.Escape] 

Visual Basic

 


_keyboard.State(Key.Escape)

The MouseState is a structure that provides three public properties for the X, Y and Z values, as well an eight byte long array for the state of the mouse buttons that is retrieved by calling the GetMouseButtons method.

Checking for the button information is the same for the mouse buttons as it was for the keyboard keys. You simply check the most significant bit at each position to determine if the corresponding button was pressed.

Visual C#

if ( 0 != mouseButtons[0])
Console.WriteLine ( "Primary Button pressed" );

Visual Basic

If Not (0 = _mouse.MouseButtons(0)) Then
Console.WriteLine("Fire!")
End If

The 0 position represents the primary button which could be the left or right mouse button depending on how the user configured the mouse.

Depending on the AxisModeAbsolute setting, the X, Y and Z values return the absolute or relative position of the mouse. Regardless of this setting the values for the X axis are always positive to the right and negative to the left, positive forward and negative backward for the Y value, and positive when spinning the scroll wheel forward and negative backward for the Z value.

Reacting to User Action

Now that we can detect what the user is doing, it is time to take that input and use it to manipulate our world. First we create a method called CheckForInput that will contain all of the input-related code. (In a later article I will show you why this is not the best way of tracking and reacting to user input and how a system called Action Mapping can be used to reduce the amount of code and avoid code duplication.) For right now we are going to set up BattleTank 2005 to use the cursor keys to change the heading and pitch of the camera (move it up/down and left/right). Note that this does not change the location of the camera. The first step is to poll the devices to retrieve the latest state information.

Visual C#

_keyboard.Poll ( );
_mouse.Poll ( );

Visual Basic

_keyboard.Poll()
_mouse.Poll()

Then we test the state for the keys we are interested in and react accordingly.

Visual C#

if ( _keyboard.State[Key.LeftArrow] )
_camera.MoveCameraLeftRight ( -0.5f );

if ( _keyboard.State[Key.RightArrow] )
_camera.MoveCameraLeftRight ( 0.5f );

if ( _keyboard.State[Key.UpArrow] )
_camera.MoveCameraUpDown ( -0.5f );

if ( _keyboard.State[Key.DownArrow] )
_camera.MoveCameraUpDown ( 0.5f );

Visual Basic

If _keyboard.State(Key.LeftArrow) Then
_camera.MoveCameraLeftRight(-0.5F)
End If
If _keyboard.State(Key.RightArrow) Then
_camera.MoveCameraLeftRight(0.5F)
End If
If _keyboard.State(Key.UpArrow) Then
_camera.MoveCameraUpDown(-0.5F)
End If
If _keyboard.State(Key.DownArrow) Then
_camera.MoveCameraUpDown(0.5F)
End If

We also want to use the mouse to move the camera, so we add another set of checks for the mouse input.

Visual C#

if ( 0 != ( _mouse.State.X | _mouse.State.Y ) )
{
_camera.MoveCameraLeftRight ( _mouse.State.X / 10 );
_camera.MoveCameraUpDown ( _mouse.State.Y / 10 );
}

Visual Basic

If Not (0 = (_mouse.State.X Or _mouse.State.Y)) Then
_camera.MoveCameraLeftRight(_mouse.State.X / 10)
_camera.MoveCameraUpDown(_mouse.State.Y / 10)
End If

You can adjust the increments used in the MoveCameraLeftRight and MoveCameraUpDown methods to make the movement slower or faster.

The final step is to add a key that enables us to exit the application. The Escape key is the natural choice:

Visual C#

if ( _keyboard.State[Key.Escape] )
{
Application.Exit ( );
}

Visual Basic

If _keyboard.State(Key.Escape) Then
Application.Exit()
End If

In addition to moving the camera around, we also want to move the location of the camera since that is how we are going to simulate driving around in our tank. The only problem is that, with no fixed items in the world, we can't really tell when we have moved, since the only reference point is the skybox, which never changes position. To overcome this limitation until we add units in the next article, I am simply writing out the new camera location to the Console whenever it changes.

Visual C#

if ( _keyboard.State[Key.W] )
_camera.MoveCameraPosition ( 10, 0, 0 );

if ( _keyboard.State[Key.S] )
_camera.MoveCameraPosition ( -10, 0, 0 );

if ( _keyboard.State[Key.A] )
_camera.MoveCameraPosition ( 0, 10, 0 );

if ( _keyboard.State[Key.D] )
_camera.MoveCameraPosition ( 0, -10, 0 );

if ( oldX != _camera.X )
{
Console.WriteLine ( _camera.X + ", " + _camera.Y );
oldX = _camera.X;
}

Visual Basic

If _keyboard.State(Key.W) Then
_camera.MoveCameraPosition(10, 0, 0)
End If
If _keyboard.State(Key.S) Then
_camera.MoveCameraPosition(-10, 0, 0)
End If
If _keyboard.State(Key.A) Then
_camera.MoveCameraPosition(0, 10, 0)
End If
If _keyboard.State(Key.D) Then
_camera.MoveCameraPosition(0, -10, 0)
End If
If Not (oldX = _camera.X) Then
Console.WriteLine(_camera.X & ", " & _camera.Y)
oldX = _camera.X
End If

The last piece of input-checking we need to add is checking the mouse buttons. For right now, let's assume that the default button on the mouse causes the tank to fire. We are going to add more exciting action later on, but all we can do now is to write "Fire!" to the Console.

Visual C#

if ( 0 != _mouse.MouseButtons[0] )
Console.WriteLine ( "Fire!" );

Visual Basic

If Not (0 = _mouse.MouseButtons(0)) Then
Console.WriteLine("Fire!")
End If

That's it for input. As I mentioned before, there are better ways of relating keyboard and mouse keys and movement to actions in the game. You may also notice that when moving the camera, sometimes a single button click causes two movements. This is due to the fact that the game loop is faster than we are and the key is pressed long enough to be picked up in two game loops. We are also going to fix that in the next article.

Summary

Once again I find myself running out of space before being able to cover all of the items I wanted, but I hope there is enough stuff here to let you experiment on your own. We are going to continuously refine the game in each iteration rather than trying to get it perfect the first time; that is the sprit of Agile development. The important things to remember are how to use the Device class to acquire and then poll an input device, and how to determine the input from the State objects.

Even though we are now able to maneuver through the 3D world, it is not very exciting, since other than the skybox there are no other items in our world. In the next article we are going to fix that by adding units, both stationary and mobile, and work on collision detection. We are also going to refine the camera to help us in reducing the number of items we need to render by tacking the frustum. Oh—yes, I do know that the targeting crosshairs are gone; they will be back in the next article in the form of a real Heads-Up-Display (HUD) class.

Until then: Happy coding.

Tags:

Follow the Discussion

  • acolyteacolyte

    I had to make up stuff as I went along to make this tutorial work. Iam glad the creator of this tutorial is not my professor or I'd be screwed.

  • RobRob

    I actually am a professional programmer and I didn't find the jump at the beginning of this section that helpful.  I refactored the code no probs...  However, the creation of classes from previous material was a stretch.  When I encountered this jump, I just went to the end of this tutorial and started flipping the code.  What might be nice is a before and after of the code in each section if you are going to optimize and refactor.   All in all, still a pretty good tutorial though.

  • JohanJohan

    Must agree with Carl, I wrote all code myself and when you start changing alot of stuff I can't follow along anymore. Else it was a good tutorial, but I am giving it up now.

  • JeremyJeremy

    I have been playing around with directX a lil while now.. And honestly, this material has taught me more about the whole framework in general. And I didn't realize till now how trigonometry played such a big role in all of this.

    In response to Carl, personally, im quite the NOOB,  you just need to be willing to experiment with everything (change numbers, delete Classes, try to crash the system Expressionless if that helps.)

    For me, the Matrices were confusing but after a look through the sources provided things start to make sense. It's just mental debugging. Develop some logical sense... Run through the provided source codes in your head. It'll eventually make sense if you get a little confused.  Don't be a loser like Johan Tongue Out

    In closing, I agree with Rob .. All in all, GOod Tutorial. . Wink

  • MikeMike

    Dude... I wanna have your babies! I dont know what all these bad comments are about. These tutorials are amazing!! Just made my first little game moving a shuttlecraft around in space, thats right... im a loser, but i bloody love it!

  • Jerry aka 10pupsJerry aka 10pups

    Does anybody just sit down and type out an app or game and poof it works?

    I am just a beginner but I understand this.

    Summary

    Once again I find myself running out of space before being able to cover all of the items I wanted, but I hope there is enough stuff here to let you experiment on your own. We are going to continuously refine the game in each iteration rather than trying to get it perfect the first time; that is the sprit of Agile development.

  • Verry LongBottomVerry LongBottom

    it builds fine but when i try to debug it says line 273 in skybox textureloader is invaliddataexception

  • JonasJonas

    The tutorial it self is very good and i really appreciate it. The problem is when a lot of stuff is taked for granted, because in the 3 first parts everything was "do this and that because". Now suddenly we have new classes that merely is introduced at all. And the code is of course not that easy to interpret at novice level. But i decided to just copy the skybox and do the other work "manually". I guess you learn better if everything had been explained but still a good tutorial so far!

    Thanks

    Jonas

  • JakeJake

    I get an error in the public Device device; line it says:

    'Device' is an ambiguous reference between 'Microsoft.DirectX.Direct3D.Device' and 'Microsoft.DirectX.DirectInput.Device' (CS0104)

    Why is the 'Device' class in both these namespaces?

  • akaimakaim

    I can't download the source file nor write it myself. Can someone help me (have the copy)?

  • Clint RutkasClint I'm a "developer"

    @akaim:  Download links are at the top of the page,  I just tried them and they work.

  • Clint RutkasClint I'm a "developer"

    This is Part 1 of an introductory series on game programming using the Microsoft .NET Framework and managed

  • Clint RutkasClint I'm a "developer"

    This is Part 2 of an introductory series on game programming using the Microsoft .NET Framework and managed

  • pp

    on my first look at tut4, I also agreed with Carl. It looked very intimidating. But then i thought... what the heck. give it a go. Took me a while, but i managed to get it to work (customising my code from the first three tuts to be the same as tut4).

    My advice to you: take ur time and read the code cleanup section slowly and thouroughly. Its not that cryptic as it looks. Dont give up. and try to understand what the code does. not just copy and paste Smiley

  • EdwardEdward

    I agree with Carl, but the tutorial is quite useful anyways.

    I am new to DirectX as most of you and also programming in general. I am trying to follow the series with November2008DirectXSDK which does not support managed environment as before as I discovered. However, it is still possible to follow the tutorial, untill now I had trouble only with timer and I wished to share how I was able to get over it. The code is working just the same as the downloaded one - as some say it is impossible to follow the tutorial in this 4th part without dowloading the code.

    Hope it helps:

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    //ForStopwatch class add:

    using System.Diagnostics;

    namespace BattleTank2008

    {

       class Timer

       {

           /*"Stopwatch.Frequency" is managed equivalent of  unmanaged "QueryPerformanceFrequency"

            *"Stopwatch.GetTimestamp ( )" is managed equivalent of unmanaged "QueryPerformanceCounter"

            *I hope I was able to apply them correctly

            *deltaTime=1000000000d/Stopwatch.Frequency*FrameworkTimer.ElapsedTicks;

            *time=1000000000d/Stopwatch.GetTimestamp ( )*FrameworkTimer.ElapsedTicks;

           */

           #region Fields

           private static bool isTimerStopped;

           private static double ticksPerSecond;

           private static double stopTime;

           private static double lastElapsedTime;

           private static double baseTime;

           #endregion

           static Stopwatch FrameworkTimer = new Stopwatch();

           private Timer() { }//No creation

           static Timer()

           {

               isTimerStopped = true;

               ticksPerSecond = 0;

               stopTime = 0;

               lastElapsedTime = 0;

               baseTime = 0;

               ticksPerSecond = Stopwatch.Frequency;

           }

           //Returns true if timer stopped

           public static bool IsStopped { get { return isTimerStopped; } }

           //Advance the timer a tenth of a second

           public static void Advance()

           {

               if (Stopwatch.Frequency <= 0) return;

               stopTime += ticksPerSecond / 10;

           }

           //Get the absolute system time

           public static double GetAbsoluteTime()

           {

               if (Stopwatch.Frequency <= 0) return -1.0;

               //Get either the current time or stop time

               double time = 0;

               if (stopTime != 0) time = stopTime;

               else time = 1000000000d / Stopwatch.GetTimestamp() * FrameworkTimer.ElapsedTicks;

               return time / ticksPerSecond;

           }

           //Get the time that elapsed between GetElapsedTime() calls

           public static double GetElapsedTime()

           {

               if (Stopwatch.Frequency <= 0) return -1.0;//Nothing to do

               //Get either the current time or stop time

               double time = 0;

               if (stopTime != 0) time = stopTime;

               else time = 1000000000d / Stopwatch.GetTimestamp() * FrameworkTimer.ElapsedTicks;

               double elapsedTime = (time - lastElapsedTime) / ticksPerSecond;

               lastElapsedTime = time;

               return elapsedTime;

           }

           //Get the current time

           public static double GetTime()

           {

               if (Stopwatch.Frequency <= 0) return -1.0;

               //Get either the current time or stop time

               double time = 0;

               if (stopTime != 0) time = stopTime;

               else time = 1000000000d / Stopwatch.GetTimestamp() * FrameworkTimer.ElapsedTicks;

               return (time - baseTime) / ticksPerSecond;

           }

           //Reset the timer

           public static void Reset()

           {

               if (Stopwatch.Frequency <= 0) return;//Nothing to do

               //Get either the current time or stop time

               double time = 0;

               if (stopTime != 0) time = stopTime;

               else time = 1000000000d / Stopwatch.GetTimestamp() * FrameworkTimer.ElapsedTicks;

               baseTime = time;

               lastElapsedTime = time;

               stopTime = 0;

               isTimerStopped = false;

           }

           //Start the timer

           public static void Start()

           {

               if (Stopwatch.Frequency <= 0) return;

               //Get either the current time or stop time

               double time = 0;

               if (stopTime != 0) time = stopTime;

               else time = 1000000000d / Stopwatch.GetTimestamp() * FrameworkTimer.ElapsedTicks;

               if (isTimerStopped) baseTime += time - stopTime;

               stopTime = 0;

               lastElapsedTime = time;

               isTimerStopped = false;

           }

           //Stop or pause the timer

           public static void Stop()

           {

               if (Stopwatch.Frequency <= 0) return;

               if (!isTimerStopped)

               {

                   //Get either the current time or stop time

                   double time = 0;

                   if (stopTime != 0) time = stopTime;

                   else time = 1000000000d / Stopwatch.GetTimestamp() * FrameworkTimer.ElapsedTicks;

                   stopTime = time;

                   lastElapsedTime = time;

                   isTimerStopped = true;

               }

           }

       }

    }

  • MikeMike

    Carl

    I would have to agree with you.   Part I, II, and III were good.  Good explanations, good explaining what the code does and where it goes and more, But when I hit part four I was thrown way off.

    In the download sample there's classes we never made and in the tutorial it never went over any off it.  Also it give code snippets but don't explain where it goes.

    The tutorial just went down hill fast.  I am not new to developing web or desktop apps but I am new to game development and direct X.  So I would like a more clearer explanation of how this all works and ties in together.

  • DaveDave

    This is actually pretty sweet how it takes those 6 simple images and blends them all together. Pain in the butt with these ambiguous names spaces in the DirectX libraries but I figured it out. Saaaawweeeet!  On to part 5!   The tutorial is pretty good actually but you definately need some experience with debugging to find the problems. I'm still using the August 2007 SDK with visual studio 2003 and everything seems to work. It's still really complicated to grasp how the actual functions work with all the coordinates that have to be manually added. Might be a good idea to memorize what each of these variables are. Also, what is the point to using an underscore (_)  at the beginning of all these objects?

           _frontFaceVertex(0).X = -100.0F

           _frontFaceVertex(0).Y = 100.0F

           _frontFaceVertex(0).Z = 100.0F

           _frontFaceVertex(0).Tu = 0.0F

           _frontFaceVertex(0).Tv = 0.0F

           _frontFaceVertex(0).Nx = 0.0F

           _frontFaceVertex(0).Ny = 0.0F

           _frontFaceVertex(0).Nz = -1.0F

  • A NA N

    Drawing the actual skybox uses the techniques we have discussed in the previous articles and I am not going to cover them in detail. The basic steps are as follows.

    1- Define a PositionNormalTextured vertex array to hold the data for the four corners of each cube face and texture information.

    2- Load the texture for each cube face from a file.

    3- Setup the Vertex buffer for each face.

    4- On each Render loop readjust the skybox.

    On each Render loop disable Z-buffering.

    On each Render loop use the Camera object passed in to determine the direction the camera is facing and draw the appropriate face.

    On each Render loop turn Z-buffering back on.

    1 to 3 were NOT mentioned earlier. can you explain these portions?

  • A NA N

    Well everyone, you've all waiting patiently 3(or maybe 4) years and after less than a month of work i've found the solution!

    Inside your loading sub use

    texture1 = TextureLoader.FromFile(mydevice, "img1.jpg")'Only load once!

    In your loop use

    device.SetTexture(0, texture1) 'Set the rendering texture for the following vertex object

    device.DrawUserPrimitives(PrimitiveType.TriangleFan, 4, CreateSquare())'

    if you've been following so far in the tutorial you know what "CreateSquare()" is for.

    Don't forget though:

    Tu is 0 to 1 for the x coordinate on the texture

    Tv is 0 to 1 for the y coordinate on the texture

    Enjoy

  • Clint RutkasClint I'm a "developer"

    @ge-force download the source code at the top of the page.  this article was designed to complement the source code, not be a step by step tutorial.

  • ge-forcege-force

    Where do you put the device.RenderState.ZBufferWriteEnable = false;

    // draw the skybox here

    device.RenderState.ZBufferWriteEnable = true;?

  • Clint RutkasClint I'm a "developer"

    @Aleksey, rereading this, this does teach and explain a lot of stuff.  This was one of our first series we did and it is a very complex subject matter.  I'd love to hear how we can improve if you'd like to contact us.

  • AlekseyAleksey

    This 4th section totally discouraged me from reading further on. Tutorials are ment to teach you how to do something and samples are to show you whats done. I do not feel that this 4th section is teaching anyone what is done. You just throw stuff out there and tell us to figure stuff on our own. Why would we bother reading this tutorial then. Might as well read comments in directx samples.

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.