|
This guide is meant to acquaint you with the features and the possibilities
of the Doom 3 engine. In a creative medium such as video games, it is really
impossible to describe every thing that can be done. A huge part of creation
is experimentation. This guide will point you in the right direction, provide
some samples, and list all the tools at your disposal. I have no doubt that
the community will take these tools and create things that will blow us away.
8/3/05 |
Compiling on Linux |
For information on how to get the SDK to compile under Linux, there is a pretty handy guide up on modwiki.net.
|
7/25/05 |
com_speeds |
Someone asked me what exactly the numbers in com_speeds mean.
frame:100 all:15 gfr:5 rf:3 bk:6
frame is the frame number, it's just an always incrementing value.
all is the milliseconds it took to render that frame (60fps is ~ 16ms)
gfr is the game code
rf is the render front end
bk is the render back end
The render front end is responsible for traversing the scene, portal and view frustum culling, shadow generation, dynamic model generation, and sorting. The back end is responsible for issuing the actual draw calls (the back end is what changes when r_renderer changes).
While I'm at it, here are what some of the other performance counters mean:
r_showPrimitives 1
views:2 draws:445 tris:11580 (shdw:3216) (vbo:0) image: 5.3 MB
views is the number of camera views that are drawn (increases with mirrors, etc)
draws is the number of draw calls sent to the video card (try to keep this low)
tris is the number of triangles sent to the video card (not as important as draws)
shdw is the number of shadow triangles sent
vbo is the number of vertex buffers used
image is the amount of image data used
r_showPrimitives 2
v:2 ds:376 t:8058/4018 v:8796/4660 st:3060 sv:12240 image: 5.3 MB
v is the number of camera views that are drawn (increases with mirrors, etc)
ds is the number of draw calls sent to the video card (try to keep this low)
t is the number of regular triangles (not shadow tris) / "ambient" triangles
v is the number of regular verticies (not shadow) / "ambient" verticies
st is the number of shadow triangles
sv is the number of shadow verticies
image is the amount of image data used
r_showDynamic
Shows the number of dynamic surfaces that were regenerated this frame (light flares, particles, guis)
r_showInteractions
This shows the number of light/surface interactions thate were generated that frame, as well as the number of shadows that were generated. Take out your flashlight while this is set to see why the flashlight makes everything so bloody slow.
|
7/18/05 |
Where to put MayaImportx86.dll |
I've had a couple of people asking how exactly to run the Maya importer. The zip file contains a single dll "MayaImportx86.dll" that goes in the Doom 3 folder along with doom3.exe. You must have Maya installed, though it doesn't have to be running when you launch Doom 3. Type "exportModels somefile.def" where somefile.def is the name of the def file that contains your export section. For more information on the format of the export section, see this page.
|
6/24/05 |
fragment program.env[0] |
Thomas writes:
"In post processing shaders used in doom 3 (like heat haze) the window space fragment position (fragment.position) is multiplied by program.env[1] to convert to 0-1 range (which would be pos*[1/width,1/height]) then it's multiplied by program.env[0] which is scaling by a non-pow-2 adjust. Now I can see why this is done b/c the game resolution is usually not a power of two so it's rendered to a power of 2 texture thus this conversion needs to be done to access the correct texel. What I'm wondering is, how is env[0] computed?"
program.env[0] is calculated as { w / Pw, h / Ph, 0, 1 } where w is the viewport width + 1, h is the viewport height + 1, Pw is the width of the uploaded texture, and Ph is the height of the uploaded texture. The 1 is added becasue an extra row and column is copied for the bilerp.
Thomas was correct that program.env[1] is calculated as { 1 / w, 1 / h, 0, 1 }
|
6/24/05 |
listDef |
James writes:
"I'm wondering how listDefs work in the Doom 3 engine. I've done plenty
of searching in the SDK, but can't seem to find how they are generated.
ListGUI.h is the most promising thing I've found so far, but a lot of the
lists (modsList, serverlist, etc.) don't seem to be in the SDK.
Are the lists handled in the exe?
Is there anyway to make up custom lists for use with listdefs?
If so, which commands do I use?"
Most of the lists are generated in the engine code (like the mod list, the server list, the save game list, etc) but there are a couple of
lists that are generated in the game code. All the lists in the PDA (the email list, the PDA list, the video list, the audio log list) are
generated in UpdatePDAInfo and AddGuiPDAData.
If you create a listDef called "foo" then setting "foo_item_0" with SetStateString will set the first item in the foo list.
When the list is rendered, it searches from 0 until it stops finding items. This means if you have 0, 1, and 3 set then the third item
will not be drawn (because it will see that 2 is not set and stop). You can get (and set) the index of the selected item with "foo_sel_0".
If multiple selection is supported ("multipleSel" is set to 1 in the listDef) then you can set the other selections with "foo_sel_%i" (which
works the same as the item data). Multiple columns are supported by inserting \t (the tab character) in the string (note that "tabstops" must
be set in the listDef).
|
6/2/05 |
Heartbeat |
There is an issue where sometimes a server will stop sending heartbeats to the master server (which causes it to drop off the list). The problem isn't major enough to warrant a new patch, but mods can fix it by putting this in idGameLocal::InitFromNewMap:
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "heartbeat\n" );
Next time there is a patch (whenever that may be), the issue will be fixed in the main engine code.
|
5/31/05 |
1.3 Auto Download Setup Instructions |
Mouse over at UMK set up a really nice
page detailing exactly how to set up
your server for autodownloads. He also is offering webspace for servers that cannot provide their own. Check it out
here.
|
5/31/05 |
Doom 3 Mod Wiki |
Looks like the guys over at Doom 3 Reference started up a Doom 3 mod wiki.
Their goal is to create a robust and complete Doom 3 editing manual. The information is contributed by community members
and can be edited by other community members. It's like an open source users manual. Head on over and contribute some
information.
|
5/31/05 |
gameLocal.sessionCommand |
Arne asks:
"Is there a reason to sometimes copy a command to the sessionCommand array
instead of using cmdSystem->BufferCommandText() to perform any commands?"
There are only 5 valid commands that can be used with sessionCommand:
- map / devmap
- died
- disconnect
- endOfDemo
- game_startMenu
"disconnect" and "endOfDemo" actually just call BufferCommandText so it doesn't really matter for those two. For the other three, you should use sessionCommand.
"map" in sessionCommand will save persistent info but "map" in BufferCommandText will not. "died" and "game_startMenu" aren't available as console commands.
|
5/24/05 |
Doom III 1.3 released |
It took longer than any of us had wanted, but Doom III 1.3 is there.
Below is the list of fixes and updates in the patch. Please refer to the
documentation after installation for complete changes information since
version 1.0
The files will be avaible shortly on id's ftp server, on doom3.com
and on the tracker.
The Linux version adds support for Doom III: Resurrection of Evil. See
the Linux FAQ for installation instructions.
We are also releasing updated SDKs for version 1.3, you can find some
documentation of the new features in the FAQ. This new SDK also has
source code for the RoE gamecode.
Fixes & Updates in 1.3:
- PunkBuster(TM) support has been added.
- EAX(R) ADVANCED HD(TM) support in the sound engine contributed by
Creative Labs(R). Doom 3 base game comes with room reverb data.
- To utilize EAX(R) ADVANCED HD(TM) in Doom 3, you must have 100% EAX
4.0 compatible sound card. Please refer to your sound card
manufacturer for details on whether or not your sound card supports EAX 4.0.
- Sound Blaster(R) Audigy(R) 2 users who wish to utilize the new EAX 4.0
feature in Doom 3 should download the latest Creative Beta Drivers for
the card released on April 5th, 2005. Not using these drivers may result
in game instability while using EAX 4.0.
- Server provides .pk4 file download URLs (http/ftp), client has
internal download.
- New class of .pk4 files: 'addon paks' are only referenced when the map
is loaded in.
- .pk4 downloads and addon paks come with a number of fixes to the 'pure
server mode' filesystem code.
- Fixed ragdoll bounciness.
- Fixed how Doom 3 detects LAN client vs. Internet clients.
- LZW compression of render demos.
- Fixed command line parameter passing.
- Added a QuakeIII-style graph of the connection quality for network clients
controlled with net_clientLagOMeter cvar
displays a graph of how much the client predicts ahead of the server
note that you can change the minimum predict ahead of a client by
setting net_clientPrediction
Changes relevant to mod developers (SDK):
- Added UploadImage to idRenderSystem interface. This lets the user blit
images to the renderer.
- Supports fs_game_base; this lets you base a mod off base game + d3xp +
your own content.
- Most of the download redirection is handled in the game code, and can
be extended.
Linux specific:
- ALSA device opened non-blocking to avoid hangs.
|
4/27/05 |
binary.conf, visportals, and 1.3 |
I've seen quite a few mods being released without a binary.conf file. Always include a
binary.conf file when you release a mod with an updated gamex86.dll! The format is quite simple, it's
a single character indicating which operating system the dll is for. 0 for windows, 1 for mac, 2 for Linux.
In 1.1 and 1.2 it doesn't matter a whole lot because the windows version will load the dll anyway, but in
1.3, the dll will only be loaded from a pak file where there exists a binary.conf file and the number matches
the operating system. If it can't find a binary.conf file, it will load the dll from base. This is probably
not what you want.
A bunch of people have asked me for some clarification on how exactly vis portals work. I put up a brief guide
Here
Every other email I get asks where 1.3 is. I understand the frustration of not being able to release an updated
version of your mod, and not being able to play any mods with RoE installed. All I can say is we're working diligently
on getting 1.3 out as soon as possible. The new SDK (with RoE game source code included) will be released shortly after.
I could tell you an exact date, but due to Heisenberg's Uncertainty Principle, as soon as I say it, it would change.
|
03/29/05 |
Load/Save GUI |
Rob asks:
"How exactly does the Load/Save game GUI menu work? Also, what is the function of "UpdateSaveGameInfo" and how does it actually
relate back to the game code?"
When the Load/Save menu comes up, the engine scans the 'savegames' folder for any files matching *.save. It then looks for a .txt
file for each .save file. If it finds one, it pulls the name from the first line of that text file. If not, it uses the name of
the file minus the .save extension. It appends the \t and timestamp of the file and sets the gui state var "loadgame_item_%i" for
each file found. The listDef is set up to look at those gui vars to populate the list.
When the user clicks on an item in the list, the menu sends an "updateSaveGameInfo" command to the engine. This command gets the
selected item (from the loadgame_sel_0 gui var), looks up that item in the list of files it scanned earlier and sets the following
gui vars: loadgame_shot, saveGameName, saveGameDescription, saveGameDate, saveGameTime. The date and time are set from the timestamp
of the .save file. The name and description are set from the first and second lines of the .txt file. The screenshot is set from
the third line of the text file or (if that line is blank) from a .tga with the same name as the .save file.
When the user clicks Load, the "loadGame" command is sent to the engine. This command looks up the selected item in the list and loads
the specified .save file. This is the same as if the user typed 'loadgame x' in the console.
When the user clicks Save, the "saveGame" command is sent to the engine. This command gets the name from the "saveGameName" gui var,
sees if there is already a .save with that name. If there is, it triggers the "saveGameOverwrite" named event. That event pops up a
confirmation dialog that triggers "saveGame 1" if the user clicks Yes. The '1' forces an overwrite. From there the code takes the
same path as if the user had typed "saveGame x" in the console.
When the user clicks Delete, the "deleteGame" command is sent to the engine. This command looks up the selected item in the list and
deletes the .save, .tga, and .txt files.
|
03/24/05 |
User info cvars |
Silly Rabbit asks:
"How are the ui_* cvars synced up to the server?"
"How does the server sync them down to the other clients?"
When the game code defines a cvar, it can set the CVAR_USERINFO flag. When the client modifies a cvar with this flag,
the network system sends a 'userinfo changed' packet to the server. The server calls idGame::SetUserInfo then repeats
that message to all clients. The clients then call idGame::SetUserInfo as well.
The userinfo block is delta compressed so only the changes are sent over the wire. If three things change in the userinfo
block in the same frame, only one 'userinfo changed' message is sent with all three values in it. This is important because
that means idGame::SetUserInfo will not be called once per change. When that function is called, every single value may have
changed, or only a single value may have changed.
Note the default implementation of idGame::SetUserInfo copies the dictionary to gameLocal.userinfo and calls
idPlayer::UserInfoChanged, so if you are adding a new userinfo cvar, chances are you will only have to set
CVAR_USERINFO and add some code to idPlayer::UserInfoChanged.
|
03/18/05 |
Getting all entities of type X |
I've gotten a few emails asking how to find all entities of a certain type. For example, how to find all light entities.
Here's some example code that works in idGameLocal:
for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
if ( ent->IsType( idLight::Type ) ) {
idLight *light = static_cast<idLight *>(ent);
}
}
You can also do some additional checks, for example you can test the exact class name by throwing in this check:
if ( idStr::Cmp( ent->GetClassname(), "flickeringlight" ) == 0 ) {
}
|
02/17/05 |
idCollisionModelManager::LoadModel |
Chris asks,
"If one attempts to load a modelname that has no collision model, does LoadModel just
load a default model, or does it load nothing and set some value on the cmHandle_t to
reflect an error, like -1?"
idCollisionModelManager::LoadModel will load the surfaces of the render model as a
collision model if a .cm file doesn't exist for that model and none of the surfaces in
the model have SURF_COLLISION set. Of course render models can have a lot of polys, and
generally make crappy collision models, so I highly suggest always using a cm or a collision
surface.
idCollisionModelManager::LoadModel will return 0 in the following conditions:
- If there are no more slots left (also calls common->Error)
- If precache is true and there is no .cm file
- If the render model is not .ase or .lwo
- If the render model doesn't exist or is invalid
|
02/01/05 |
Deforms |
sprite | Deform the triangles of this surface to always face the viewer |
tube | Pivot a rectangular quad along the center of its long axis Note that a geometric tube with even quite a few sides tube will almost certainly render much faster than this, so this should only be for faked volumetric tubes. Make sure this is used with twosided translucent shaders, because the exact side order may not be correct. |
flare <size> | Build a translucent border that's always facing the viewer. This only works on quads (surfaces with 4 vertices). Used to make lens flares around lights. |
expand <amount> | Expand the surface along it's normals. |
move <amount> | Moves the surface along the X axis, mostly just for demoing the deforms. Example: deform move (time * 1) |
turbulent <table> <range> <timeoffset> <domain> | Turbulently deforms the S, and T values. Example: deform turbulent sinTable 0.05 (time * 1) 10 |
eyeBall | Each eyeball surface should have an separate upright triangle behind it, long end pointing out the eye, and another single triangle in front of the eye for the focus point. The texture coordinates on the eyeball surface will be deformed so that the center is lined up with the vector going from the origin triangle to the focus triangle. |
particle <particleDecl> | Emit particles from the surface instead of drawing it. |
particle2 <particleDecl> | Like particle, but ignores the surface area so small surfaces emit the same number of particles as large surfaces. |
|
01/31/05 |
Map Loading |
When you issue a "map" command, the following process happens:
- Mute Sound System
- Run Wipe
- Capture screen to "_scratch"
- Display "wipeMaterial" for "com_wipeSeconds" seconds.
- Unload Previous Map (Call idGame::MapShutdown)
- Show the loading gui for the next map (guis/map/____.gui)
- Initialize Render World
- Parse Map File
- Build lists of portals and models
- Populate areas with models (but don't load them yet)
- Clear user input buffers
- Set user info (idGame::SetUserInfo and idGame::SetPersistentPlayerInfo)
- Call idGame::InitFromSaveGame or idGame::InitFromNewMap
- Call idGame::SpawnPlayer
- Load all deferred data
- Load all models
- Load all images
- Load all sounds
- Load all decls
- Load all guis
- Call idGame::RunFrame 10 times to let things settle
- Create static light interactions
- Run Wipe
- Capture screen to "_scratch"
- Display "wipe2Material" for "com_wipeSeconds" seconds.
- Un-Mute Sound System
The question that prompted me to post this was, "How can I display the previous screen the player saw
when switching levels?" Now that we know how the loading process works, the easiest way to implement that
would be to use "_scratch" as the background in "guis/map/____.gui"
|
01/28/05 |
Controlling Monsters |
Grim asks:
"I am creating a map now, and when ever I get a gun and shoot it off all the monsters
come running tward my location from all over the map. Is there anyway to make it to where
they stay in one spot until I open a door or walk into the room?"
There are a couple of ways to do this.
If you set "ambush 1" on the entity, then the monster will only attack if it sees the player
(it ignores sounds). Alternatively, you can set "hide 2" on the monster, then set up a trigger_once
that targets the monster. When the player hits the trigger_once, the monster will unhide and wake up
(this can also help speed up the game a bit because when the monster is hidden, the game can skip
processing it).
There are other flags that you can set on the monsters. For example, you can set it so the
monster is visible, but still doesn't attack until triggered, useful if you can see the monster
through a window. You can also make the monster play an animation when it's triggered, or play
the 'teleport' effect. You can even control what type of teleport effect it plays. You can get
a list of all the key/value pairs with a brief description in the entity info window in Radiant.
|
01/14/05 |
dmap options |
glview | Not implemented |
v | Print extra information as the map is compiling |
draw | Render the level as it's compiling (not sure if this works anymore) |
noFlood | Don't 'flood' the level marking outside surfaces invisible |
noLightCarve | Don't carve geometry based light volumes (default) |
lightCarve | Carve the geometry based on the volume of the lights that touch them |
noOpt | Don't optimize (merge and cut) triangles |
verboseentities | Print extra information about entities (more so than with just verbose) |
noCurves | Don't process patches |
noModels | Not implemented |
noClipSides | For debugging, don't clip the sides of a brush to other solid parts of the world |
noCarve | Don't cut up any surfaces (like adding noFragment to every surface) |
shadowOpt <n> | Set the shadow optimize level:
0 - No optimization
1 - SO_MERGE_SURFACES (default)
2 - SO_CULL_OCCLUDED
3 - SO_CLIP_OCCLUDERS
4 - SO_CLIP_SILS
5 - SO_SIL_OPTIMIZE
|
noTjunc | Don't fix t-juctions. (Triangle optimization won't work without t-junction fixing) |
noCM | Don't generate .cm (collision) information |
noAAS | Don't generate .aas (pathfinding) information |
editorOutput | Pipe status messages to the editor window |
|
01/06/05 |
What noSelfShadow does |
The way the renderer handles noSelfShadow is it:
- Renders all selfShadow objects shadows to the stencil buffer
- Renders all noSelfShadow objects with lighting
- Renders all noSelfShadow objects shadows to the stencil buffer
- Renders all selfShadow objects with lighting
So basically what happens is when 'noSelfShadow' objects are rendered, their shadows haven't been rendered to the stencil buffer yet. But by the time 'selfShadow' objects are rendered, all shadows have been rendered (self and otherwise).
This means an object with 'noSelfShadow' will not cast a shadow onto a different object with 'noSelfShadow'
In other news, we're back from the holidays :)
|
12/09/04 |
WARNING: Backwards Triangle Generated! |
This message is generated when the optimizer is generating triangles and finds one with
a zero or negative normal. It is a fairly benign warning. Most often this happens because
the optimizer generated a degenerate triangle [a triangle with no surface area] because
of floating point error.
To put it in John's words:
"This can happen reasonably when a triangle is nearly degenerate in
optimization planar space, and winds up being degenerate in 3D space."
In other news, me and others from id are going to be out of the office much of this month
due to Christmas, New Years, Chanukah, and other such holidays. Emails sent to us may not
be answered for a while.
|
12/02/04 |
Maya Importer Source Code |
Looks like the last installer was missing some files to compile the Maya Importer.
I have put up an installer which contains only the missing files here.
|
11/23/04 |
More Clean Pack Files |
Eutectic is at it again, he has a clean skin pack here
and a clean shound shader pack here.
|
11/18/04 |
Clean def Files |
Eutectic cleaned up all the def files, removing entities that are invalid, and providing more accurate comments
for the valid ones. You can download his pack file here. Also,
if you missed it, he also has a clean materials pack here.
|
11/16/04 |
SDK Version 2 -- Now With Vehicles! |
We just packaged up a newer version of the SDK.
You can find the Windows version here
The Linux version is here
Vehicles
Be sure to read the readme.txt in the vehicles folder
Maya Importer Source
To compile the Maya Import dll, you will need to get the Maya 4.5 or the Maya 6.0 SDK from Alias.
There is a note in the directory explaining where the files need to go. The project comes
set up to compile a dll for Maya 4.5. If you want to compile a dll for Maya 6.0,
instructions are in maya_main.cpp. For Maya 6.0, you will need Visual Studio 2003 or higher.
The Maya 6.0 SDK does not work with Visual Studio 2002.
Linux Source
TTimo put together an official release for the Linux code. The code itself is the same as windows,
but there are some different build options and an included SCons file.
Random Fixes
Including (but not limited to) compiling 'out the box' in Visual Studio 2003 and 2005
If you have already done significant work on a mod, I would recommend installing this to a clean
directory, then running windiff against your code base. Alternatively you could diff against the
first SDK and manually copy the changes over.
|
11/12/04 |
Maya Importer Round-up |
There has been a bit of confusion over the Maya importers, so to set it all
straight: You need the import dll that matches your version of Maya. If you
are running Maya 4.5 then you need the MayaImportx86.dll for Maya 4.5.
Here are import dlls for all the versions of Maya I could find:
|
11/12/04 |
Rendering Order |
Mathias was curious as to the exact render order for the different
stages in a material. In particular, what happens with multiple
diffuse or bump stages. Here's a very high level overview on how
the Doom 3 engine renders:
- Step 1:
Renders all solid (non-translucent) geometry in black. If a stage
has an alpha test, then it gets rendered with the alpha test enabled.
This is to fill the depth buffer so early z can prevent expensive
shader ops later.
- Step 2:
Render all light interactions.
For each light {
Render shadows into the stencil buffer;
For each stage in the light material {
For each surface in the light volume {
inter.diffuse = inter.specular = inter.bump = NULL;
For each stage in the surface material {
if stage is Diffuse {
if ( inter.diffuse && inter.bump ) Render( inter );
inter.diffuse = thisStage;
}
if stage is Specular {
if ( inter.specular && inter.bump ) Render( inter );
inter.specular = thisStage;
}
if stage is Bump {
if ( inter.bump ) Render( inter );
inter.diffuse = inter.specular = NULL;
inter.bump = thisStage;
}
}
Render( inter );
}
}
}
- Step 3:
Renders any stage with "blend" set to something other than "diffusemap",
"specularmap", or "bumpmap". This is where guis and other translucent
or alpha blended surfaces get rendered (including particles).
- Step 4:
Render any stage that references _currentRender. This would be heat
haze, glass distortions, and other crazy post process effects.
To stay safe, I would always put my stages in the following order:
bump, diffuse, specular. That way you'll always know how it will
get rendered.
|
11/11/04 |
Adding a new event |
To add a new event to an entity, for example idPlayer, there are four things you
will need to do:
Create a new idEventDef
Open up Player.cpp, at the top you will see a whole bunch of lines similar to this:
const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" );
The first string is the name of the function in the script, the second string describes
the parameters it takes (in this example an Entity and a Trace). It can be NULL if the
function doesn't take any parameters. The last character is the return value. It can
be omitted (such as in this example) for functions that return nothing.
Add a new function
You will need to create a function in your class that has the same function signature
as the event def. For example, a SpectatorTouch function would need to take an idEntity
pointer and a trace_t pointer. All event functions should return void because there is
a special syntax for returning values back to a script (idThread::ReturnInt(0);).
Add a new event map entry
You need a way to tie your script event to the function you added. A line such as this
will add your event to your function:
EVENT( EV_Gibbed, idPlayer::Event_Gibbed )
Add an entry to doom_events.script
Lastly, the scripting system needs to be aware of this new event. You have two options,
the first is you can open up doom_events.script and add your event function signature
to the very bottom (or top). The second (better) option is you can add your event signature
to a new file and #include it at the top of doom_main.script.
|
11/11/04 |
Tutorials List |
This is a bit old, but still really handy. The people over at Doom3World.org
have a gajillion tutorials up, mostly on level editing, but there's a few on
modeling or editing guis.
You can find the
master list here.
|
11/02/04 |
Using Fonts, Part 2 |
One of the problems with the code I posted yesterday was it only renders fonts
in a single size (small). The following code adds the ability to render fonts
using a specified size (which is a floating point value with 1.0 meaning 48pt).
Use "seta hud_textsize " to change the text size. Note that for brevity, I just
hard coded the thresholds for small and medium font, but the engine uses the
cvars specified in the comment.
Also note that inside the engine "english" is used for french, german, spanish and italian,
so to create a completely correct system, you'd need to use the "english" font for
all 5 of those languages.
Code in bold changed from yesterday.
const char *lang = cvarSystem->GetCVarString( "sys_lang" );
const char *fontname = "an";
const char *message = "This is a test";
float x = 20.0f;
float y = 300.0f;
float size = cvarSystem->GetCVarFloat( "hud_textsize" );
renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f );
fontInfoEx_t fontInfo;
renderSystem->RegisterFont( va("fonts/%s/%s", lang, fontname), fontInfo );
fontInfo_t *fontInfo2 = &fontInfo.fontInfoLarge;
if ( size <= 0.30f ) { // gui_smallFontLimit
fontInfo2 = &fontInfo.fontInfoSmall;
} else if ( size <= 0.60f ) { // gui_mediumFontLimit
fontInfo2 = &fontInfo.fontInfoMedium;
}
float scale = size * fontInfo2->glyphScale;
for ( const char *p=message; *p; p++ ) {
glyphInfo_t &glyph = fontInfo2->glyphs[*p];
renderSystem->DrawStretchPic( x, y - glyph.top * scale,
glyph.imageWidth * scale, glyph.imageHeight * scale,
glyph.s, glyph.t, glyph.s2, glyph.t2,
glyph.glyph );
x += glyph.xSkip * scale;
}
|
11/01/04 |
Using Fonts |
Trevor was asking how he could render some text directly to the screen without having
to use the GUI code. Here's a simple example (paste it at the bottom of idPlayerView::SingleView)
const char *lang = cvarSystem->GetCVarString( "sys_lang" );
const char *fontname = "an";
const char *message = "This is a test";
float x = 20.0f;
float y = 100.0f;
renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f );
fontInfoEx_t fontInfo;
renderSystem->RegisterFont( va("fonts/%s/%s", lang, fontname), fontInfo );
for ( const char *p=message; *p; p++ ) {
glyphInfo_t &glyph = fontInfo.fontInfoSmall.glyphs[*p];
renderSystem->DrawStretchPic( x, y - glyph.top,
glyph.imageWidth, glyph.imageHeight,
glyph.s, glyph.t, glyph.s2, glyph.t2,
glyph.glyph );
x += glyph.xSkip;
}
Of course you'd want to wrap that up in a nice function and cache the fontInfoEx_t
so you wouldn't have to do the lookup every time, but you get the idea.
A similar question, that I have been asked a few times is how to create these
fonts. The structure is the same as it was in Quake 3 Team Arena, and
Arnout van Meer of Q3F (and now of Splash Damage) wrote a special tool for
creating them. Check out q3font
|
11/01/04 |
Game overwritting the dll, Part 2 |
Romout offers an other suggestion for solving the 'game overwriting the dll' problem:
You can simply create a post build process similar to this one:
./PostBuild "$(TargetPath)" "e:\games\doom3\MODNAME\$(TargetFileName)"
Where PostBuild is a .bat looking like this:
@echo off
attrib -r -s -h "%2"
echo Copying from %1 to %2
copy /Y "%1" "%2"
attrib +r "%2"
|
10/28/04 |
Where the client ends the server begins |
Chris asks a pretty straight forward question: "where does client end and server begin?"
The executable (doom3.exe) and the dll are both run on the server and all the clients.
This is different from Quake 2 where the dll only ran on the server, and different from
Quake 3 where qagame only ran on the server and cgame/ui only ran on the client. Having
the dll run on both makes a lot of things much easier, for example prediction.
Client prediction was a bit of a pain in Quake 3 (and almost nonexistant in Quake 2), but
with Doom 3, the client can predict everything because it is literally running the same
code that the server is running. The physics system is run on every machine, so a lot
of things that don't matter (such as shattering glass or falling shells) can be run on
the client side without taking up any bandwidth.
This programmability gives a lot of power to mod authors, as you can now do things that
were simply impossible in the previous engines, but with great power comes great problems.
Autodownloading of a client side dll is rather difficult. A dll is native executable
code, which means it is very possible to contain a virus. Autodownloading a virus would
make a lot of people very unhappy. To run a mod, the client has to download and install
it from another source (a website). If a client tries to connect to a game with a different
dll than the server is running, he will get a "Data not in sync with server data" message.
|
10/28/04 |
TGA's and DDS's |
A few people have asked about the TGA and the DDS files. DDS is a Direct Draw Surface
file, which is basically a texture that is precompressed (using S3TC aka DXTC) and has
all the mip maps pre-generated. The TGA file is the uncompressed version. The TGA files
are used when running in high or ultra quality mode (in high quality, the diffuse maps
use the compressed DDS files, but the normal maps use the uncompressed TGA files).
You can force everything to load from the TGA files by setting "image_usePrecompressedTextures"
to 0. The images will still get compressed dynamically at load time (which will slow down
loading significantly), but it will make it so that when you change a TGA it will actually
show up in game without having to regenerate the DDS files. You can also set
"image_useCompression" and "image_useNormalCompression" to 0, which will prevent them from
getting compressed at load time (this is similar to running in ultra quality mode).
For generating the DDS files, we use The Compressonator from
ATI.
It has a nifty command line interface, which is useful for generating a lot of DDS files at
once. The commands "startBuild" and "finishBuild" along with "image_useOfflineCompression"
will generate a batch file (makedds.bat) that contains the commands needed to generate DDS
files for all the images that were referenced.
nVidia has some useful tools for viewing DDS files (including a plugin for Photoshop), which
you can find on this page.
|
10/28/04 |
Deform Tube |
Brendon Chung asks:
"In an attempt at creating grass & tree foliage, I am using a DEFORM TUBE sprite.
However, while DEFORM TUBE works fine with BLEND ADD, it doesn't seem to work with BLEND DIFFUSEMAP (the sprite simply disappears)."
For those that are confused, deform tube is a deform that will rotate the surface
around its major axis (the longest side) so that it's always facing the camera. This can create
a cool looking "tube" effect (hence the name). Note that a geometric tube with even quite a few
sides will almost certainly render much faster than this, so it should only be for faked
volumetric tubes.
The problem that Brendon is running in to is that, when the new quad is generated, the winding
order may not be correct. For this reason, it should only be used with two sided translucent
shaders.
|
10/28/04 |
How materials affect sounds |
Inside the material declaration, you can put one of sixteen different surface types,
ten of which are predifined types: none, metal, stone, flesh, wood, cardboard, liquid,
glass, plastic, ricochet. The other six are generic: surftype10, surftype11, surftype12,
surftype13, surftype14, surftype15
The surface type in the material determines which sound is played for footsteps, projectile hits, and other types of impacts.
The footstep sound is determined by looking at the surface type for the material under the
actor. For example, if it is "flesh" then the "snd_footstep_flesh" sound in the actor entityDef
is played. If that sound is not defined then "snd_footstep" is played instead. In Doom 3, we
don't actually use this feature (all footsteps play the same sound).
The projectile impact sounds are handled the same way. If a fist hits a material with "glass"
set, then the "snd_glass" sound is played from the "weapon_fist" entityDef. If that sound is not
defined then it uses "snd_metal". If that sound is not defined either then it uses "snd_impact".
If that sound is not defined then it plays nothing.
This is all defined inside the game code. As a mod author, you can completely change up this
entire system. To see where this is all defined, search the code base for "sufaceTypeNames".
|
10/26/04 |
Game overwriting the dll |
Some people have been having issues with their game dll being overwritten. The
reason for this is the dll in the pack file is always used instead of the dll in
the directory. The way around it is to always zip up your dll, but that's obviously
a pain. The other way around it is to put your dll in the doom3 folder next to
doom3.exe (in other words, not in your mod folder). You'll only be able to play
your mod, and you won't be able to play on pure servers, but it tends to be a lot
easier to develop this way.
|
10/26/04 |
Debugging the dll |
We are aware that the copy protection system prevents mod authors from debugging
the game dll. We're looking in to a fix for that right now. The only way to really
debug is with the shotgun printf approach. I understand that's a really hacky way to
program, and wish I had a better answer.
|
10/26/04 |
DOOMEdit 80% Memory Error |
I've gotten some reports of people getting the following error when they have a lot
of memory in their system (like 2gb): "Physical memory is over 80% utilized. Consider
saving and restarting". I looked in to it, and apparently the < sign should have
been a > sign (in other words, the error only appears if more than 80% of your
memory is free). It should be fixed in the next patch, but until then you can pretty
much ignore the error.
|
10/26/04 |
Compiling on Linux |
A couple dozen people have asked me about getting the SDK to compile in Linux. We
have an SCons file to build the code, but it has a lot of stuff for the main engine
in there, which is why we didn't release it with the SDK. Dante over on the Doom 3 World
forums wrote up a make file that seems to do the trick. You can find more information
about it here.
|
10/26/04 |
_button7 doesn't work |
As some people have discovered, BUTTON_7 is always set to false. I just checked
the code and there was a < 7 where there should have been a <= 7. I fixed it, but
until the next patch, you'll just have to use buttons 0-6.
|
10/26/04 |
Where did my toolbar go? |
If you manage to close your toolbar in Radiant, and can't seem to get it back,
open the registry editor (Start->Run, "regedit", OK) and delete the following
key and all it's subkeys: HKEY_CURRENT_USER\Software\DOOMRadiant
|
10/19/04 |
The Vehicle |
I've gotten quite a few emails asking about the vehicle we talked about at Quakecon.
That is going to be released some time in the near future. We didn't have it packed
up in a state that could be easily distributed, and we really didn't want to delay
the SDK any more. I'll post it here as soon as I can.
|
10/19/04 |
MayaImportx86.dll |
It looks like we completely forgot to include the file MayaImportx86.dll in the SDK. oops.
You can find it here.
It needs to be extracted to the same folder doom3.exe is in (C:\Doom3)
This should make the exportModels command magically start working.
|
10/19/04 |
Mirrors |
If you are having issues downloading the SDK from our site, here are a few mirrors:
File Planet
3dgamers
File Shack
gamershell
FileFront
|
10/19/04 |
Compiling in Visual Studio.NET 2005 |
Wow, the repsonse to the release has been incredible, I'm sifting through the emails now
and will post up some of the more common ones later, but the topic I got the most email
about was compiling in Visual Studio.NET 2003 / 2005. We developed Doom 3 with VS 2002,
and there have been some subtle changes to the compiler, which cause some warnings and
errors. In order to get it to compile without errors, the following changes
have to be made:
In PlayerView.cpp, line 527 needs to change to:
float shift = scale * sin( sqrt( (float)offset ) *
g_dvFrequency.GetFloat() );
In PlayerView.cpp, line 662 needs to change to:
int offset = 25 + sin( (float)gameLocal.time );
Additionally, there's apparently an internal compiler error in idLib with 2003. This appears
to be a bug in the compiler which can be solved by making the following change (thanks
Beafy):
Change line 5385 in matrix.cpp from this:
sum = sum * sum + v[0]
To this:
double v0 = v[0];
sum = sum * sum;
sum = sum + v0;
|
10/15/04 |
Where do I get the SDK? |
I'm sure this is the question that everyone wants the answer to...
Here
|
10/07/04 |
How should I install Doom 3? |
If you plan on modifying Doom 3 at all, I highly recommend installing it in
C:\Doom3. A lot of the built in tools get really upset if there are spaces
in the file name, so installing it in "Program Files" is not really an option.
As for the pack files, I recommend leaving them packed up, and only unpacking
the files you need. This will keep your install pure so you can play on pure
servers, and it also keeps your directories clean. It makes the things you
modified much easier to find than if there were a bunch of standard id files
laying around. Since the tools (like DOOMEdit) are all built in to the game,
they don't care if the files come from the directory or from in a pack file.
I would also recommend keeping all your modified assets in a seperate folder,
such as "mymod" rather than in "base". This makes reinstalling much easier
and allows you to seperate your stuff that you're working on from the default
game stuff and stuff other people have made. You can create a shortcut to
Doom 3 with +set fs_game mymod in the "target" so you don't have
to keep selecting your mod every time you start the game.
|
9/30/04 |
Why are some textures black in the editor? |
If you've played around with radiant much, I'm sure you've found more than a
few textures that show up as black when used. You may be asking why those are
still in the game when they obviously don't work. The short answer is all those
textures exist here at id, but we only copy the images to the CD that are actually
used in the game (using the cvar fs_copyfiles). Even if a material is not
actually used in the game, the material text file will still get
copied over, but the images won't be. This is because a single text file can
contain materials that are used as well as materials that are not used.
Eutectic created a custom pack file that cleaned up a lot of the missing shaders,
which you can find here.
|
9/28/04 |
My Radiant's broken! |
When you open Doom 3 Radiant, if you see a white screen instead of a grid,
turn off antialiasing (r_multiSamples 0). Nine times out of ten that fixes it.
|
9/26/04 |
More on doom_main |
If you are not planning on using any of the id maps in your mod, then you should
really remove them from doom_main in order to speed up compile time and cut down
on memory usage. If you are making a multiplayer only mod, you can further cut
it down by removing the monster scripts. Here is a base doom_main that could
be used for a multiplayer only mod:
// base defines and util functions
#include "script/doom_defs.script"
#include "script/doom_events.script"
#include "script/doom_util.script"
#include "script/weapon_base.script"
#include "script/ai_base.script"
// weapons
#include "script/weapon_fists.script"
#include "script/weapon_pistol.script"
#include "script/weapon_shotgun.script"
#include "script/weapon_machinegun.script"
#include "script/weapon_chaingun.script"
#include "script/weapon_handgrenade.script"
#include "script/weapon_plasmagun.script"
#include "script/weapon_rocketlauncher.script"
#include "script/weapon_bfg.script"
#include "script/weapon_soulcube.script"
#include "script/weapon_chainsaw.script"
#include "script/weapon_flashlight.script"
#include "script/weapon_pda.script"
#include "script/ai_player.script"
If you are making a total conversion, you can further slim it down by removing
the weapon scripts.
|
9/22/04 |
How do I add a weapon? |
One of the first things that any mod maker wants to do is add a new weapon. Luckily, Doom 3
makes this much easier than it has been in the past. In fact, for most weapon types, you won't
even need to open up the code -- it can all be done through scripts. For our new weapon let's
make a fireball, that way we can reuse the current models and sounds.
The first thing you'll want to do is open player.def. If you remember on Monday we found out
that weapon 13 isn't quite as usable is it looks, so move weapon_pda to slot 13, and set
"def_weapon12" to "weapon_fireball". "weapon12_cycle" should be 1, and all the else should
be 0. Next, scroll down to the "weapon" key and add "weapon_fireball" to the list after
"weapon_pda", this will make us start with fireballs (since there aren't any fireball pickups
on any maps, we have to do it this way or cheat by typing "give weapon_fireball").
Next we begin the process of duplicating the hand grenade:
- Create a new file named weapon_fireball.def
- Start off by copying the weapon_handgrenade entityDef and name it weapon_fireball
- Change the following keys from "grenade" to "fireball"
- model_view
- model_world
- def_dropItem
- inv_weapon
- weapon_scriptobject
- def_projectile
We'll leave ammoType as grenades for now
- Copy over moveable_item_grenades and rename it to moveable_item_fireballs, making sure to
change "inherit" in the process.
- Copy over worldmodel_grenade and rename it to worldmodel_fireball.
- Copy over viewmodel_grenade and rename it to viewmodel_fireball.
- Copy over projectile_impfireball (from monster_demon_imp.def) and rename it to projectile_fireball.
- Copy over damage_impfireball and damage_impfireball_splash (also from monster_demon_imp.def) and rename
them so they are not imp fire balls. Change the references in projectile_fireball to point to the new name.
Change the damage for both to 100.
- Make a copy of weapon_handgrenade.script and rename it to weapon_fireball.script. Open
weapon_fireball.script and use find/replace for handgrenade to fireball.
- Be sure to add #include "script/weapon_fireball.script" to doom_main.script
At this point if you run your mod, you should be able to cycle to something that looks like a hand grenade,
but shoots fireballs.
The biggest problem with it now is it takes grenade ammo, and as we all know fireballs don't take any
ammo at all (they are summoned from hell). We must rectify this situation. Open player.def and set
"weapon12_allowempty" to 1. Inside the weapon_fireball, remove "inv_ammo_grenades", set the "ammoType"
to "", and set "ammoRequired" to 0. You should now be able to fire as many fire balls as you want.
Now the problem is it still looks like a grenade when you are holding it. If I were an artist, I would
create a new model that looked less like a grenade and more like hands waving around, but since I'm a
programmer I'm just going to put a random lava texture on it. Create a new file in the 'skins' folder
called 'skins_fireball.skin' and inside it, put:
skin skins/models/weapons/fireball {
models/weapons/grenades/grenades3 textures/hell/lavascroll_ns
models/weapons/grenades/grenades3fx textures/hell/lavascroll_ns
}
skin skins/models/weapons/fireball_invis {
models/weapons/grenades/grenades3 textures/hell/lavascroll_ns
models/weapons/grenades/grenades3fx textures/hell/lavascroll_ns
models/characters/player/arm2 models/characters/player/arm2_invis
}
skin skins/models/weapons/nofireball {
models/weapons/grenades/grenades3 textures/common/nodraw
models/weapons/grenades/grenades3fx textures/common/nodraw
}
skin skins/models/weapons/nofireball_invis {
models/weapons/grenades/grenades3 textures/common/nodraw
models/weapons/grenades/grenades3fx textures/common/nodraw
models/characters/player/arm2 models/characters/player/arm2_invis
}
Then in weapon_fireball, change the skin_nade, skin_nade_invis, skin_nonade, and skin_nonade_invis keys to
point to the new skins we just made.
Once we do this, the fireball actually looks pretty good as long as we throw it overhanded, but quick tosses
look kind of funny. This is an easy fix, just open weapon_fireball.script and change FIREBALL_QUICKTHROWTIME
from .2 to 0.
There you go, your own weapon in the game. You can actually place these in radiant now, but if you want to
do that then I would recommend not making them have infinite ammo (how to add ammo may be covered later, but
it's not any more difficult than adding a weapon). The hardest part of adding a new weapon is getting it
modeled and animated correctly. I would strongly recommend prototyping all weapons using existing art.
If you are having problems, you can download my version and
compare it against your own version.
|
9/21/04 |
I added my script but it's not working! |
If you create a new map, weapon, or monster you're probably going to want to add a new script file to go along
with it. However, if you just drop the .script file in the scripts directory you will notice that it doesn't
get recognized automatically.
The scripts directory does not just get scanned and loaded automatically. The
only file it loads up is doom_main.script, that file then includes all the other script files. If you want to
add your own script, you need to be sure to #include it in doom_main.script!
|
9/20/04 |
Why can't I select weapon 13? |
If you look in player.def, you will see that the weapons go up to 15, but weapons 13, 14, and 15 are
unused. To select a weapon 3, you use _impulse3, for weapon 9 it's _impulse9, etc... So if you add
a new weapon 14, you may think you can select it with _impulse14 but that's not actually the case.
Impulse 13 is reload weapon, impulse 14 is prev wepon, and impulse 15 is next weapon. You can only
use impulse commands to select weapons 0-12. Weapons 13, 14, and 15 cannot be selected directly
(they can only be cycled to).
Why was it done this way? To be completely honest, it just kind of happened. We didn't use the last
3 weapon slots so we didn't think about not being able to select them directly. Oops.
There is some good news, though. The PDA can be opened with _impulse19 (which is showscores in multiplayer),
so you can move the PDA up higher in the list, freeing a slot for a new weapon. The really good news
is all this is handled in the game code, so mod developers can change it up however they want. It is
not likely to ever get changed in the main game code though, because that would require moving reload,
prev, and next to different impulse commands, thereby breaking everyones config files.
|
9/19/04 |
The Code |
Just a quick update, I added a new section for The Code
|
|