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.
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.
In addition to these general housekeeping changes I added some code that I needed to make the DirectInput portion more interesting.
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.
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;
Dim worldMatrix As Matrix = Matrix.IdentityDim viewMatrix As Matrix = cam.ViewviewMatrix.M41 = 0.0FviewMatrix.M42 = 0.0FviewMatrix.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.
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.
_device.SetStreamSource ( 0, faceVertexBuffer, 0 );_device.VertexFormat = CustomVertex.PositionNormalTextured.Format;_device.SetTexture ( 0, faceTexture );_device.DrawPrimitives ( PrimitiveType.TriangleStrip, 0, 2 );
_device.SetStreamSource(0, faceVertexBuffer, 0)_device.VertexFormat = CustomVertex.PositionNormalTextured.Format_device.SetTexture(0, faceTexture)_device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2)
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 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.
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).
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.
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.
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.
_device = new Device ( SystemGuid.Keyboard );
_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.
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.
_device.SetCooperativeLevel ( form, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive );
_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.
_device.SetDataFormat ( DeviceDataFormat.Keyboard );
_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.
try{ _device.Acquire ( );}catch ( DirectXException ex ){ Console.WriteLine ( ex.Message );}
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.
private KeyboardState _state;
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.
keyboard.Poll()
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 );}
Try _device.Poll() _state = _device.GetCurrentKeyboardStateCatch generatedExceptionVariable0 As NotAcquiredException Try _device.Acquire() Catch iex As InputException Console.WriteLine(iex.Message) End TryCatch 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.
if ( _device != null ) _device.Unacquire ( );
If Not (_device Is Nothing) Then _device.Unacquire()End If
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.
_device = new Device ( SystemGuid.Mouse );_device.SetDataFormat ( DeviceDataFormat.Mouse );private MouseState _state;public void Poll ( ){ _device.Poll ( ); _state = _device.CurrentMouseState;}
_device = New Device(SystemGuid.Mouse)_device.SetDataFormat(DeviceDataFormat.Mouse)Private _state As MouseStatePublic Sub Poll() _device.Poll() _state = _device.CurrentMouseStateEnd 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.
_device.Acquire ( );_device.Properties.AxisModeAbsolute = false;
_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 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.
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; }}
Dim gameControllerList As DeviceListgameControllerList = 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() NextEnd 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.
_device.SetDataFormat ( DeviceDataFormat.Joystick );private JoystickState _state;public void Poll ( ){ _device.Poll ( ); _state = _device.CurrentJoystickState;}
_device.SetDataFormat(DeviceDataFormat. Joystick)Private _state As JoystickStatePublic Sub Poll() _device.Poll() _state = _device.CurrentJoystickStateEnd 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.
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.
_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.
if ( 0 != mouseButtons[0]) Console.WriteLine ( "Primary Button pressed" );
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.
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.
_keyboard.Poll ( );_mouse.Poll ( );
_keyboard.Poll()_mouse.Poll()
Then we test the state for the keys we are interested in and react accordingly.
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 );
If _keyboard.State(Key.LeftArrow) Then _camera.MoveCameraLeftRight(-0.5F)End IfIf _keyboard.State(Key.RightArrow) Then _camera.MoveCameraLeftRight(0.5F)End IfIf _keyboard.State(Key.UpArrow) Then _camera.MoveCameraUpDown(-0.5F)End IfIf _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.
if ( 0 != ( _mouse.State.X | _mouse.State.Y ) ){ _camera.MoveCameraLeftRight ( _mouse.State.X / 10 ); _camera.MoveCameraUpDown ( _mouse.State.Y / 10 );}
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:
if ( _keyboard.State[Key.Escape] ){ Application.Exit ( );}
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.
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;}
If _keyboard.State(Key.W) Then _camera.MoveCameraPosition(10, 0, 0)End IfIf _keyboard.State(Key.S) Then _camera.MoveCameraPosition(-10, 0, 0)End IfIf _keyboard.State(Key.A) Then _camera.MoveCameraPosition(0, 10, 0)End IfIf _keyboard.State(Key.D) Then _camera.MoveCameraPosition(0, -10, 0)End IfIf Not (oldX = _camera.X) Then Console.WriteLine(_camera.X & ", " & _camera.Y) oldX = _camera.XEnd 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.
if ( 0 != _mouse.MouseButtons[0] ) Console.WriteLine ( "Fire!" );
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.. 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.
If you would like to receive an email when updates are made to this post, please register here
RSS
I am new to directX and as such I have the February 2007 SDK.
TextureLoader is now deprecated and i was wondering if you could post and alternative for loading textures from bitmaps. I have looked all over and cant get this to work.
I was going along fine,I managed part 3.I don't know how you expect anyone learning to program to be able to create all those classes,write reams of code and correctly edit part 3 to fit part 4.I'ts so typical of tutorials.They start of simple and rapidly become too complicated.There's no other comments on part 4 because nobody's done it.The only people who could do it are proffesional developers,but why would they bother.I was enjoying this tutorial on Direct X and i'm quite dissapointed it's turned out like this.
You can download the source code for the project at the top of the page. All you have to do is model your code after that.
I agree with Carl. I not even can do the changes from the old article to this. :/
Got to agree with Carl here. Just had to download the new project as it was impossible to follow the modifications.
Looking through the new code it all makes sense, but its not really "learnt" as I'm not doing it.
i agree...i was also following it very fine but got into this problem u explained and now i can no longer follow. i must say that this series of tutorials is damn good, but when they take big jumps then its hard to keep up...i hope they consider it.
forgot to say that of course you can play with the code that you can download for the tutorials
True I stopped programming by this part (can only do so much in a day!) and I'm downloading the examples at the top of the page and running them...not much point typing out the code in this page in my own project (it won't copy properly when I cut & paste en masse which is annoying) yet I can still understand what the author is going on about. This has some very useful foundations for a fully fledged 3D game - not a bad effort for a tutorial series. My only expereince with 3D engine guides is a cryptic 1000-page text book and broken examples on the provided CD - in comparison this is brilliant!
Yes, the articles have jumped vastly ahead. It is my hope to fill in the gap. I'm a professional developer and would personally hate to see anyone get discouraged because of the sudden learning curve. More to come...
Man you are doing a really great job here,but this article is way too advanced,you are trying to teach DirectX noobs to develop a game so don't get into the advanced stuff now,i didn't even continue 1/4 of this article,i tried but i don't understand many things.
and thanks again for the articles
Not true Carl, it's simple, just follow what was written for whatever language you are using. To aid, the code is even given at the top you can use to orient how your code is looking to his.
Besides, this isn't an introductory tutorial on programming, you should have a very good grasp on basic programming before you go into more advanced tutorials such as DirectX, managing classes is a week1 topic in any intro to x development class, and that's all he's doing at the beginning.
Also, this is the method that is generally used, you write the base engine first, then you go in and nitpick within that your more specific events, then the UI or whatnot, and it has to slide within certain parts of the engine code that was already written. And reams of code? This is actually very little code compared to what I've seen written for the same program. =\
I'm afraid he's right. The only way I got past this was by downloading the code and looking over it for a while to figure out what everything did.
Yeah, this part kinda lost me right from the start. For one thing I had problems following what was changed in the code cleanup part. I have downloaded the source code to look at to see whats happened but it going to take me a long time to track everything. This part could have been much better written.
The code means nothing because it doesn't tell you where each bit should go. Its all out of context.
I don't think your supposed to update the code from earlier stages by following the tutorial. Download the code for each lesson at the top of the page and follow along looking at the changes.
That is true.
I have program experience (Client/Server applications) with another language and I'm here trying to learn how to Make a games in C# in one Tutorial that is named "Beginning Game Developer".
Of course, I can search the methods that you used in example and try to see what do you did in that classes, but you could explain better your changes because it was a large step for someone who are learning.
Im the same mind of Carl, the 3 part was still easy and understanable, but this 4 part goes litle bit too hard. And i even have litle bit experiens in programing but still i dont understand this, where should put all thoose statements?
Agree with Carl. All was good in Part 3, then comes part 4! Had to check and make sure I hadn't missed a section (I hadn't). Got too complex too quickly
Ok, I really agree with Carl. I was getting everything up to this point but then you completely lost me. You start giving lines of code with no indication of where to put it.
i agree with carl, this is stupid. up untill now i was going fine. then you just expect someone who is just learning to manage to create all of those classes and all that code!
Just wanted to say great tutorial. It gave me exactly the info I needed (only using Direct Input). Got gamepad input up and going in no time.
I agree with Carl. The changes from version 3 to version 4 are mentioned as if they are simple changes, but completely altering the structure of the code mandates more specific explanation.
Specifically, the new 'camera' class refactoring could use quite a bit more explanation.
Derek,
I would have to agree with Carl. This tutorial was going along nicely, and I was able to easily follow everything very well. Once I went to Part IV you lost me with all of your changes.
"In addition to these general housekeeping changes I added some code that I needed to make the DirectInput portion more interesting." Nowhere do you note these changes, what changes you made, you just said you did it.
I have over eight years of programming experience in several languages and even I couldn't follow this section after Part III, so I feel bad for any beginner attempting to learn game programming with this tutorial.
If you make a tutorial for beginners (even though your difficulty is "intermediate," your title proclaims "Beginning Game Development") you need to be more direct and show what needs to be done, not just state it and expect someone to follow.
If it was best to seperate several functions into classes/files, you should have not only explained that part in the previous lessons, but had the audience program that way since the beginning.
You've lost my interest in continuing this tutorial, I believe several other people felt the same after reaching this section. What a waste of about an hour of my life that I can't get back.
I also don't even want to hear about downloading your files to coordinate with your tutorial, that isn't how a tutorial works. A tutorial teaches you step-by-step to reach a specific goal, not tutor you half way then give you the answers so you can reach that goal. That has no learning value.
I agree, I was following along nicely, and even was understanding what was going on. I have done my fair share of programming, and I do it professionally, however; the just basically scrap parts 1-3 and "redo" them is just plainly retarded. This act by the author is the main reason that game companies never hit their expeted time goals.... Is that the real point of this tutorial? To teach advanced developers how to program like the gaming industry, late and over budget????
I agree with last comment because "Drawing the actual skybox uses the techniques we have discussed in the previous articles" is not true. We havent covered loading textures yet.
See ya.
I still like this tutorial, although it's a little complicated now. The difficulty is Intermediate,so it's not suitable for beginners.
I'm a beginer, but I think this tutorial can help me to reach intermediate level.
I can agreee with Carl's comments.
Though I did make it through the code cleanup changes up to "In addition to these general housekeeping changes I added some code that I needed to make the DirectInput portion more interesting." at which point I throw the toys out of the pram :).
It would have been nice to have the a copy of the code with the "general housekeeping" changes made but prior to any additions made in this section of the tutorial. Making it possible to follow the changes through the tutorial and have the fun of creating some working code rather than having the completed tutorial presented on a plate.
I would have to agree with Carl, I was happily going through the tutorials, got through number 3 them BAM, I have no idea what's going on. Saying I was disapointed would be an understatement.
I agree with Carl. There's such a gap between part 3 and part 4. So many new methods is involved here. I think it's better to seperate this chapter and describe in detail.
I appreciate your articles, but only for Part 1-3. I was baffled here, as a newbie.
Carl, game programming is not an easy subject, and most people who attempt to learn it with out some key programming background will find it difficult. The author of the artical clearly labeled the difficulty as Intermediate, and not beginner. I suggest you spend some time with VB or C#. get to know OOP. Spend some time with the system.IO class. Learn about GDI+, and the drawing classes. Work with a small database project. Once you have some good practice with .Net this tutorial won't be so bad.
Carl, with respect, this is *not* a tutorial for anyone that is learning to program.
I'm confusing, I don't know which class was created or edited. There is a difficulty to identify the portion of code explained, my suggestion is write the class.method and position of the codes. this tutorial is very interesting, but sin to requires special knowledge. I think that should get organized better.
I hope than you understand me.
Obrigado.
Carl, you can download the source codes from the top of this page
jepp, it's just as carl said...
:(
Nice tutorial for fast solutions.
Not looking it like handbook.
Good job!
It would be better if the code is commented... at least a brief explanation of what each method does and why.
I have to agree with the above comment, this tutorial series completely changed changed pace and format with this tutorial. I'm slowly managing to understand this tutorial by comparing my code up against the downloaded one, but I don't think that this is the way that someone should be learning something.
Sadly, this is where I gave up. Parts 1, 2 and 3 I had managed to do by following the instructions, only compiling the provided code to check mine did the same. But the refactoring etc. made at the start of this part were beyond my abilities to deduce for myself. I tried, but, for example, I didn't know that in order to move the timer to "HiResTimer" I had to declare
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport ( "kernel32" )]
public static extern bool QueryPerformanceFrequency ( ref long PerformanceFrequency );
You should provide code from the /start/ of the part, after your refactoring, and various alterations, but /before/ doing the changes talked through in the tutorial. That way people will still be able to work through each section from the code provided.
Providing the code as it should be at the /end/ of the tutorial is less useful, as it means people can't work along with the instructions, which is presumably the point.
I hate to dignify posts such as Carl's but I must regrettably agree. I am a professional dev'er. I first found this series quite a while ago and tried to do it. However, I found the errors impossible to get around. I'm a much better dev'er than I used to be and I've recently decided to dabble in game programming.
In my most recent attempt at this series, trying to make the transition between parts 3 => 4 has been particularly troublesome. At the very least, the download code for 4 should show where you should be at the beginning of 4 vs the end. Not just the end result. Way too much is left out. Especially for novices.
To those struggling with these: keep trying. Although some of the omissions here are frustrating, there's a lot of good info here. Try if you can.
I agree with Carl. We got along without all those extra classes before, why do we need them now? Especially for a tutorial. We can do that on our own once we understand how to use this stuff and wish to apply it to something that would be more suitable for our own creations. You may be demonstrating proper coding methods, but this is an intermediate tutorial designed for those already familiar with cleaning up code. You should've had the tutorial already planned.
I have to agree with Carl here, big jump without explaining much for some novices like me...
Carl is right... this is ridiculous. You made way to massive of a jump from part 3 to part 4. I am totally lost. FYI, I am not a beginning programmer. I am quite competent with c# and I am a full-time web developer. I am, however, very new to DirectX. You can't expect anyone to follow along and then when they get to part 4, you say, "Oh, by the way, I made this massive yet vague list of changes from part 3 to part 4." You could at least include a base project file for part 4 and then the completed project file for part 4. Tutorial was going great, but you royally screwed up.
I agree with Carl and I too am disappointed. The tutorial started off explaining everything, where the code goes, but what happened here?
It begins with
"I refactored all code that deals with the device in the OnPaint method of the GameEngine class into the Render method. "
What do you mean by 'refactoring'? What is the 'Render method'?
And you no longer tell us where the code goes.
You did a good job so far, please edit this to be like the previous parts.
I agree with Carl.
Where does cam suddenly come from?
Well in my case some keys are not responding (WSAD) and on start I have a message :
A first chance exception of type 'Microsoft.DirectX.DirectInput.AcquiredException' occurred in Microsoft.DirectX.DirectInput.dll
To Carl : I believe you have to follow the curse here and download the project. Then you have to learn also about C#.
Aren't the normals on _backFaceVertex[0].Nz supposed to go the other way?
Please look at your SkyBox.cs and you will find this:
_frontFaceVertex[0].Nz = -1.0f;
_backFaceVertex[0].Nz = -1.0f;
And I have to agree with Carl, this is an example of how NOT to write a tutorial (of any kind).
True. How are you supposed to learn any of this stuff if you end up just copy+pasting code from the tutor's download into your own code? There are so many revisions from the preceding lesson to this one that it was an exercise in futility hand coding from the sparse instructions supplied, which is a shame.
Also some of the changes (like code movements from one class to another) mentioned at the start of this tutorial don't even marry up to code contained in the classes -it's only from checking the source download for this tutorial that you can see where the discrepancies lie.
Sadly i can't afford to buy a second monitor and gfx card for my computer so i can check and compare the code supplied in the download with my own AND follow this tutorial in a web browser =/
Hopefully Part V won't be as hard to follow as this....
You lost me on this article. I thought the first three articles were great but all the "housekeeping" has me lost in this one. You don't explain how you made any of the changes that you made. I tried figuring it out from the sample code but it doesn't look remotely similar to the code from before. I hope that you add instructions on how to make these changes in the future. After all, if this stuff is so simple that you didn't think we would need help with it then it should be simple to add.
I have to agree with Carl, I was really enjoying the tutorial up until this lesson. I downloaded the source and I was completely lost at what you had done. Sorry if it sounds like I'm moaning at you Derek, thanks for the effort you put into this.
I was doing fine until chapter 3. The code for chapter 4 was rewriten so I had to download. I know the code is organized but this not relevant, important is understand the concept here. So my sugestion is to keep the code simple. Also why the member variables are declared at the end of the class, since everybody does at the top. Open any OO book and you will see the member at the top of the class. Also why the if's are tests backward like if (0 = var1). Same thing is there any reason for that, other that beign different?
Whew! That was a lot to take in. I slightly agree with Carl that the quality of the tutorials are decreasing as the series goes along. But at the same time I have to admit that as I took the time and look over the code carefully and experiement a little I am still not lost.
I'd like to see these articles again after a few editors and experts got their hands on them. They were very easy to follow up until this one. Keep up the good work!
One question I have is when you write to the Console where am I supposed to view that output? Is there a console output window in VS that I am missing?
I agree that the implicit rewriting of code was overly complex, and that I was hard pressed to carry it out. Finally, I gave in and downloaded the files from the link at the top of the page, and spent my time "glueing" the new files into my existing code. I would encourage anyone else with this issue to do the same, it saves time.
I found this part difficult to follow as well like Carl mentioned.
i've seen this code before why make it look harder then it is. comment the code more here i read about it in a older book its not hard you just make it seem hard but the code is great i like it it helps me convert me visual basic 6.0 code to vb.net 2005 express edition better.
I agree with Carl. I think this part instantly became complicated, mainly because it didnt tell begineers where to enter the code. I am also disappointed and maybe this part and the rest should be redone.
I am a little confused about the code cleanup perhaps you could explain how to do that more in depth
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.
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.
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.
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 :| 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 :P
In closing, I agree with Rob .. All in all, GOod Tutorial. . ;)
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!
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.
it builds fine but when i try to debug it says line 273 in skybox textureloader is invaliddataexception
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
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?
I can't download the source file nor write it myself. Can someone help me (have the copy)?
@akaim: Download links are at the top of the page, I just tried them and they work.
This is Part 2 of an introductory series on game programming using the Microsoft .NET Framework and managed
This is Part 1 of an introductory series on game programming using the Microsoft .NET Framework and managed
PingBack from
PingBack from
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 :)
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
double elapsedTime = (time - lastElapsedTime) / ticksPerSecond;
lastElapsedTime = time;
return elapsedTime;
//Get the current time
public static double GetTime()
return (time - baseTime) / ticksPerSecond;
//Reset the timer
public static void Reset()
if (Stopwatch.Frequency <= 0) return;//Nothing to do
baseTime = time;
isTimerStopped = false;
//Start the timer
public static void Start()
if (isTimerStopped) baseTime += time - stopTime;
//Stop or pause the timer
public static void Stop()
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;
}
}
}
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.