Ultimate Game Programming with DirectX - First Edition
This is a guide to errors, issues and workarounds I encountered or had to implement when working through the first edition of "Ultimate Game Programming with DirectX".
This was written up in something of a hurry whilst on the train so if you see any confusing senetences, or don't understand something or alternatively if you have some errata of your own to add please contact me via twitter @_delph or leave a comment at the bottom, thanks!
Warning: I am going to be very brief in this write up, I am not going to say which exact lines need to changed and may or may not mention needing to change things dependant our changes, this is in the hope that if you're following and have been reading the book what I've written should make sense. If you're not following the book / don't own this book or are not taking the time to understand the logical structure of the programs I doubt you'll be able to get much out of this! Also with the exception of the first chapter these are the errors / issues with the game project and I have not included problems with the demo applications.
Intro Chapter:
Lines Demo - Using D3DFVF_XYZ as our custom vertex structure does not work (honestly I'm not sure why this is the case, answers on a postcard please). We can make the demo work by changing to using D3DFVF_XYZRHW instead.
We also need to change the struct for our vertex data to contain the additional term and accordingly adjust our setting of the object data (just put 1 for the value of rhw).
Please note that while using 'col' as a variable name (even just in the scope of a init function) does not cause problems here it will in later chapters, when windows.h is included, so I would recommend using colour or color as your variable name.
Game Project Notes Chapter:
The first issue you'll probably encounter is the difference between the virtual functions for create static buffer and render and their D3D counterparts, the former uses unsigned int for the static buffer ID and the latter simply uses int. This Id is just the index of an array so it would seem to make sense to change the D3D function to use unsigned int, but trust me you'll save yourself a lot of issues later if you change the virtual function to be an int (the author assumes that it is an int for the rest of the book).
You'll also need to set m_staticBufferList = NULL in the constructor of your D3DRenderer class, otherwise it will try to delete it before there is any data in it when the shutdown function is called when initialising (yes, he calls the shutdown function before the initialise function presumably to 'clean up').
The Create FVF function and the custom FVFs defines are not in the code from the book, you can grab the necessary functions from the example code or later chapters.
You may wish to add SetCursor(LoadCursor(NULL, IDC_ARROW)); before you initialise the engine in the windows code else you'll likely end up with the spinner cursor whilst your in the project/game.
Lighting Chapter:
First thing, you'll need to add an EnableLighting function to the engine if you wish to use lighting as the default initialise function does not have lighting enabled so even if you create and set various lights as enabled you won't see anything! (Alternatively you could add a lighting flag to your init function). Do this now so you don't wonder - like I did - when you've finished the project and start trying to use lighting why everything is still full bright!
Also if you plan to use lighting in your game project built off this engine you're probably going need to write a light manager so that you don't try and use more hardware lights than your hardware can cope with but that's for a different article.
My IDE didn't like it when I used the suggested variable name of 'l' so yours might too, it's probably a good idea to something a bit more descriptive anyway for code readability and reserve single letter variables for indexes etc.
He defines a UGPCOLOR in the game project which is supposed to be the same format as the D3DCOLOR types but does not rely on the direct x headers (so you can define colours in the game code and maintain code separation the game and the of implementation of the engine), however I've never been able to get his macro to work and he doesn't explain how it is supposed to work so I would suggest simply passing 3 (rgb or 4 for rgba) ints to your engine instead of the long and converting into a D3DCOLOR there.
Textures Chapter:
As with the static buffer list you should set m_textureList = NULL in the constructor of the class. Additionally numTextures (as well as numStaticBuffers) should be an int rather than an unsigned int.
He also neglects to mention various functions and files you need to add into your project at this point (again you can grab them from the source on the CD):
- Add FtoDW function to D3DRenderer file
- Add Set Texture Filter to OneTimeInit()
- Add structs.h to project and add in the structs created in this chapter
If you plan to use multitextures you'll need to add a Multi Texture Vertex Format to the project.
GUI Chapter:
The CreateText and DisplayText functions are missing from the Renderer if you simply follow the book, as is creation of the font object and the variables for displaying text. For these functions you'll need to create versions using r,g,b,a rather than color arguments, but you'll a version of display text that uses a single d3dcolor for communicating with the GUI system.
There's a 'col' hanging about watch out for it!
Special Effects Chapter:
Just one issue here the code has UGP_MS_SAMPLES_2 where it should say UGP_MS_NONE. Also his code for setting samples either gives you what you ask for or none. I wrote a something that gives you the highest supported if what is asked for is not supported, you may want to do the same!
Scripting Chapter:
Note that I always set string pointers to NULL instead of 0 on init and will be doing so for the rest of the book.
Property Scripts
- The CVariable Destructor should be moved to a Shutdown function and be called when necessary otherwise the when your are copying your variable it will delete the data! Be careful that do call shutdown when necessary or you may get memory leaks
- Modify parse function to take Var type as an argument and to not stop on spaces if it's a String var
- Modify the Load file function to ignore blank lines as well as comments
- I suggest a Scripting.h which includes all the scripting files and adding this to your engine.h
Command Script
- Note when using the Parse String Param you must initialise the string first (foo = new char[MAX_PARAM_SIZE];) (this would be excuted by a script interpreter class if there was one however at the moment we are writing the logic directly into the game code so you'll have to do it yourself).
- Move stVector to structs.h and include this file in your scripts (rather than defining it in multiple places).
- Again need to check for blank lines - modify "return" in
if(currentLineChar >= (int)strlen(m_script[currentLine])) return;to{ destCommand[0] = '#'; destCommand[1] = '\0'; return; }
Token Script
You need to include <string.h>.
Maths Library Chapter:
I've not found any errors here!
Collision Detection Chapter:
You need to add a blank GetPlanes function for Bounding Sphere to prevent linker errors.
Direct Input Chapter:
Apparently Microsoft now recommends that you use RAW input for keyboard and mouse and use direct input only for gamepads / joysticks you may wish to investigate this, I've not got around to this at the time of writing.
The initialise function always fails if there isn't a controller connected. This isn't really desired behaviour! Also if you try to use a joystick it will probably constantly rotate right, I think this is because of the way input of the second analogue stick is handled for a gamepad, but I've not investigated this yet.
- Change POINT in get mouse pos to nullPoint from null.
- Add Input Interface and Direct Input headers to engine.h
- Add #define KEYS_SIZE 256 to defines.h, and UGP_LEFT_BUTTON and UGP_RIGHT_BUTTON defines (0, 1)
- Add Process Input to main.h
- Add g_hInstance to globals and set to wc.hIstance after creating application window
- Add g_ mousex, y, lmbdown etc if you do not already have them
- Set m_device to NULL in constructor for Game Controllers
GetCursorPos() only works in fullscreen mode. Add the following code before mouseX = etc, but after GetCursorPos()
// Adjust for windowed
if(!g_FullScreen)
{
RECT WindowPosition;
GetWindowRect(g_hwnd, &WindowPosition);
pos.x -= (WindowPosition.left); // Adjust for window position
pos.y -= (WindowPosition.top); // Adjust for window position
}
DirectSound Chapter
WARNING - The August 2007 DirectX SDK was "the final release of the DirectX SDK that will contain the following components: DirectMusic". - you will either need to download this SDK or implement a different system. I implemented FMOD system instead I can happily recommend it as easy to set up.
Model Loading Chapter
He does a bit of refactoring at this point so you'll need to rename the Render function to RenderStaticBuffer and update any uses of it.
When following the book it will appear that in the overloaded UMF loading function it has FreeOBJModel which should have alerted you to something being wrong! At this point he has skipped from writing about the UMF loader to the OBJ loader and has completely skipped the end of the UMF loader and most of the OBJ loader, you can grab the full code for both from the book source code.
- Remember to add #include "UMFLoader.h", #include "OBJLoader.h" to D3DRenderer.h and include the relevant files in the project
- Replace UGPCOLOR with D3DCOLOR (it's fine as it's in the D3DRenderer file)
- LoadXModel should return UGP_FAIL not false
- Add ReleaseAllGUIs() to the RenderInterface.h
- Add numXModels = 0, and xModels = NULL to your CD3DRenderer constructor
- If your writing code as your going along like I did (rather than copying his files) you'll end up with the main menu music playing twice as it will be in the process input function as well as in initialise main menu.
Model Animation Chapter
I found no problems here, be aware if you are skipping the demos and just doing the adding to the game project sections (naughty!) this chapter is pretty much just including the files you write in the demos into the project.
'Finishing' the Engine Chapter
Again this chapter is mostly a case of including the files from the demos in this chapter into your project!
The Camera class is arguably too specific for the engine, you should really have a game camera class and then move functions that define how the camera moves and other behaviours to this class.
Despite being covered in demo the Frustum is not included anywhere in the project if you do include it, it will error! I decided to include it in MathLibrary.h (although scene management possible should have it's own section) and to fix the errors I changed the protected variable of Frustum to a pointer and added a constructor which sets the Frustum to an array of six Planes and a destructor to delete them. You need to include windows.h in Frustum in order to set the pointer to null.
Making the Game Chapter
This chapter has many stops and starts and the project rarely compiles when he says it should (quite often it needs code from the next section to work). I'll describe the error I encounted in the order I found them and let you know when each time we finish a section.
In my version Screen Size and fullscreen attributes are variables rather than defines, if you've done this too make sure you include the extern globals in the relevant head files. Also I copied the code that I'd already written in previous sectiosn into the files where I thought they would go and then adjusted as I went along, rather than re-writing it all from scratch so you may find that you've already did some of these and I neglected to do them as I went along!
- the section for game.cpp is entitled gameEngine.cpp, which ain't right!
- because of my GetWindowRect in game.cpp (the adjusting for not fullscreen) needed to add extern of g_hwnd to game.h and include windows.h
- needed to include game.h in level.h for stGameWorld
// Up to Loading and Displaying of Final Level
- add the creation and setting of backdrop for the loading GUI to the initialize main menu function if you didn't already
- also add definition of GUI_LOADING_SCREEN to menu.h
- update GameReleaseAll function and add MainMenuRender() to switch.
- rotZ.Rotate arguments in LevelRender should be rotZ, 0, 0, 1 not rotZ, 0, 1, 0)
- If you want the loading screen to actually render when loading, add MainMenuRender() before the GameReleaseAll in the GS_LEVEL_1_SWITCH part of game loop.
When adding the command LoadStaticModelAsX and LoadAnimatedModelAsX make sure that you allocate the memeory in the g_gameWorld structure (i.e. delete any m_models if they exist then g_gameWorld.m_models = new stGameObject[totalModels] in between initial reading of the script and loading the data into structures).
I accidently put an invalid command into the script because of this I noticed that it doesn't fail gracefully if the level doesn't load, so in the switch in game loop, for the GS_LEVEL_1_SWITCH state, in the else (i.e. level load failed) statement, add InitializeMainMenu before setting the game state back to the main menu.
Project will not compile at this point because there is no camera! Add one as a global to game.h / game.cpp and include camera.h from the engine - I also took the opportunity to add a constructor to CCamera that set pos(0,0,0) view(0,0,1) up(0,1,0), if you try to apply view to a camera with 0 for all of these, you just get a black screen! He sets this manually as part of the load level function in the next chapter - which should be done anyway even if you implement a default.
// Up to Camera Movements and Collision Detections
- I don't know why Calculate Proj Matrix was added to Game Engine Initialise, seems unnecessary.
- Add TestCollision declartion to game.h, this is not technically necessary if you include the function before the "game process input" function in the source, but I like to do it.
- Add virtual function to RenderInterface for Get X Model Bounding Sphere
- In the worldMat setting in the load level (moved from render level) the rotZ should probably be around the Z axis (same error as before)!
// Up to Game Elements
- Add UpdateXAnimation to render interface
- Remember to add the globals to gameEngine.cpp too
- Clear artifacts in that same way as models
- add externs to level.h of fonts (and if you have variable win height / width then add those too).
- add TestArtifactCollision to game.cpp, it is the same as TestCollision function but with m_artifacts[i].m_id in place of m_models[i].m_id.
// Up to Agent Characters
- Add Loading of characters to the script file! Same way as artifacts.
- m_totalArtifacts = 0 should be m_totalCharacters = 0 in the updates to game.h
- Update GameReleaseAll with gameWorld.ShutDown() (this probably should have been done earlier!).
- I changed g_totalEnemiesKilled to m_totalEnemiesKilled to more accurately portray it's scope - I was tempted to rename it to totalCharactersKilled (...it's not like these agents are aggresive, oh they work for the government, they must be evil!).
- I added m_visible(1) to the constructor of Game Object structure so objects are by default visible.
- include Ray.h from your engine in game.h
- I had to alter the loading of shotSound to suit my SoundSystem class.
- Add overloaded declaration for CharacterTestCollision to game.h
- You'll want to add the visible check to your other character test collision function, unless you want to run into invisible agents!
- I've added a very basic ShowCursor(false) to the level switch state and ShowCursor(true) on pressing Esc during GS_LEVEL in the process input function
- make sure m_numArtifactsCollected is set to 0 in shutdown function.
When including sounds for the weapon shot I noticed he'd moved the menu sound (in the file structure of the project) without telling us, feel free to do the same (it was in the menu directory) and update the code.
// Other issues
The skybox x model in the final chapter is slightly flawed - if you implement mouse look like I did you'll notice when you look up!
- Add maps/ to the file name for the top direction on line 174 (in the x Model file for the skybox)
- Rotate the up direction by 90 degrees clockwise by changing lines 95 to 98 to:
0.000000;0.000000;, 1.000000;1.000000;, 0.000000;1.000000;, 1.000000;0.000000;,
Agents still spot you when dead! - to adjust for this add && g_gameWorld.m_characters[i].m_visible to the condition in process input which adds to the spotted count if you collide with an enemy.
The game doesn't actually use direct input to get mouse position, but then again he doesn't implement mouse look and using windows get cursor pos is perfectly fine for use with a GUI.