Post tutorial Report RSS Simple model creation in SOF

This is an official tutorial written by Keith Fuller from Raven Software in 2000. It documents how to create a simple model in code for Soldier of Fortune.

Posted by on - Advanced Props Modelling

Tutorial originally written by Raven software. Mirrored here for archival purposes.


Keith Fuller

Programmer, Raven Software

3/17/00

SIMPLE MODEL CREATION IN SOF

-or-

You and Me and Ghoul Makes Three

Note: any time I use a word or phrase you’ll actually find in the code,

Ill try to put it in Courier font, like this

Intro

This little tutorial assumes the existence of whatever model(s) you want to work with, meaning they already exist as Ghoul (.GHL or .GHB) files. Here we’re only dealing with simple models, stuff like tables, benches, propane tanks, etc. Human, cows, dogs, and such use similar concepts, but due to their complexity they employ radically different creation methods.

An Example Model

For starters, open up the file g_generic.cpp. It’s found in the gamecpp folder of the SOF project directory.

Let's take a computer keyboard (the item known as misc_generic_comp_keyboard, its code is found in gamecpp/g_generic.cpp) as an example. Once you have your Ghoul model (in this case we're using base/ghoul/objects/generic/comp_keyboard/keyboard.ghb) ready for use, make sure it's somewhere in the base/ghoul folder.

Model Data

To fit into the current SOF system you’ll need to add an entry to genericModelData, the array that keeps track of all of the important data for a given model. Every model in this array has its own #defined index…the index for the keyboard is OBJ_COMP_KEYBOARD, and its entry in the array looks like this (sorry about the margins)…

"objects/generic/comp_keyboard", "keyboard",   SURF_METAL,
MAT_WALL_GREY,  HLTH_GEN_COMP_KEYBOARD,  SOLID_BBOX,  NULL,    
0, DEBRIS_SM, NULL, // OBJ_COMP_KEYBOARD

The important stuff you’ll need to worry about is really just the first couple of items. "objects/generic/comp_keyboard" refers to the subdirectory under the base/ghoul directory in which you’ll find the Ghoul model we’re using. "keyboard" is the Ghoul model itself…the .ghb file.

The rest of the array entry you can monkey with also, but it primarily deals with how the object behaves with respect to physics and damage.

SURF_METAL is the surfacetype of the object…if it’s metal, the keyboard will spark when shot, if it’s SURF_GLASS it’ll throw off shards of glass.

MAT_WALL_GREY is the material type…what kind of debris will be created when the thing dies (if it becomes debris when it dies. Most things do.). The material type only indicates the skin that’ll be used on the debris…the actual size of the debris chunks is determined later.

HLTH_GEN_COMP_KEYBOARD is the health of the object, occasionally overridden in the code.

SOLID_BBOX is the solid type for determining how other objects will interact with the keyboard. SOLID_BBOX means the keyboard is treated as being solid and its shape is defined by a bounding box.

The next item is a NULL pointer in this case, but occasionally refers to what’s called a material file. A material file, known by its .ifl extension, is kept in the same folder as the model and contains a simple text list of the different skins (.tga files) available for a model. As long as the model is created in Max to point to a .ifl, you can list any .tga you want in the .ifl and switch to them at any time by calling certain Ghoul functions in the game code. IMPORTANT: When creating an .ifl, make sure you hit return after the last .tga in the file, or that .tga won’t get loaded and won’t be available to you. I point this out because it’s a very easy bug to introduce and is very hard to track down later.

The next datum, 0, is the number of pieces of debris you’d like this object to break into when it dies. If you put a 0 here, as is the case with the keyboard, a default number of debris chunks will be generated based on the volume of the object’s bbox. You can override it in the genericModelData array if you want, as is done with, say, the bench model OBJ_BENCH_BOTTOM.

DEBRIS_SM is one of several possible values indicating the size of each chunk of debris generated.

The final piece information is a NULL and, as far as I know, will always be a NULL. It used to refer to something useful earlier in the project, but I’m pretty sure it doesn’t serve a purpose anymore.

Spawning An Object – the Spawn Function

Once we have a genericModelData entry for the model, we’ll need a spawn function for it, a chunk of code that gets called when the map is loaded and it’s time to create an instance of our object and place it in the world. Spawn functions are name after the object they spawn and begin with “SP_”, so the spawn function for the keyboard in our example is SP_misc_generic_comp_keyboard. For most objects the first order of business is creating the correct bounding box, which we do for the keyboard with the following code:

VectorSet (ent->mins, -4, -11, -2);
VectorSet (ent->maxs, 4, 11, 2);

Next, we perform the actual step of assigning a Ghoul model to our object with a function that means exactly what it says:

SimpleModelInit2(ent,&genericModelData[OBJ_COMP_KEYBOARD],NULL,NULL);

The important things to note about this call are: 1) we’re passing in the object we’re creating 2) we’re passing in all of the necessary model data that we stored in genericModelData . The other parameters are things you can play with later if you like, selecting a specific skin for the model and turning on a specific part of the object.

The next line of code sets up a function that’ll be called when our keyboard gets shot.

ent->pain = comp_keyboard_pain;

In this case, a function called comp_keyboard_pain which, as it turns out, is simply a wrapper for a generic pain function used for most small objects, Obj_painflip.

The spawn function is also where you would set up other functions for an object such as death functions, touch functions, and think functions. Examples of this are scattered throughout this same file.

One other thing we need to do with our spawn function is put it in a list with various other spawn functions so the game’s spawning code knows what to do when it finds a misc_generic_comp_keyboard in the map. To do this, look in the genericSpawns array near the top of g_generic.cpp. You’ll see that every object listed in genericModelData also has an entry in genericSpawns consisting of the object’s name followed by the object’s spawn function. The order of the entries in this array doesn’t matter but it’s a good idea to keep it in the same order as genericModelData to make it easier to read.

Editor Information

Now go back down the file to the object’s spawn function code. Just above the spawn function is a good place to put the information that the map editor needs to know about or object. The information is a C comment and begins with the word QUAKED. When you load up the editor it looks at all of the game source code files and loads all of the object information in all of the QUAKED comments scattered throughout the project. Here’s the QUAKED info for our keyboard:

/*QUAKED misc_generic_comp_keyboard (1 .5 0) (-4 -12 -2) (4 12 2)   INVULNERABLE  NOPUSH  x x x x FLUFF
A keyboard to a computer.
------ SPAWNFLAGS ------
INVULNERABLE - can't be damaged.
NOPUSH - can't be pushed
FLUFF - won't show if gl_pictip is set
*/

Name and Size

As you can see, the name of the object is displayed first, followed by three sets of three numbers. The important stuff to know here is that the name you put right after QUAKED is the name that’ll show up in the editor, and the second and third sets of numbers, in this case (-4 -12 -2) (4 12 2), define the size of the object as seen in the editor.

Spawn Flags

After the numbers you will often see several words in all caps…these represent spawn flags for the object, values that you set either on or off in the editor. The definition of the flags is often given later on in the comment. Here you can see that our keyboard can be either invulnerable or not, unmoving or not, etc. The x’s in the list are just place holders with no effect in the code, and the FLUFF flag lets you know that, when set, this object won’t be rendered on low end systems so as to cut down the on-screen poly count and give you a better frame rate on graphically-disadvantaged machines. After the list of flags there is a brief description of the object…”A keyboard to a computer”. Everything after this point is just going to be a comment displayed in the editor, so it’s a great place for any useful information you want to convey to the level designer.

Where To Go From Here

Now that you have your object in the game, find a level designer to place it in a map for you. Load the map and check out your object!

If you want the game code to do more things with objects like change their animations or change their skins, think of one of the objects you’ve seen in the singleplayer SOF maps and hunt it down in the code. If you’re not sure what it’s called, open up that particular singleplayer map in the editor, find the doodad you’re after, and note its name. Find that object in the code and try to understand what its spawn code and its associated functions (like die, touch, and pain) are doing.

You may also find it helpful to look over the various model functions listed in g_obj.cpp, also in the gamecpp project.

ADVANCED STUFF

Don’t read any further unless you really, really mean it. Consider yourself warned.

Intro

OK, so you’ve messed around with the kiddy stuff…you modeled a tissue box in Max, exported it, coded it up, and placed it in a map. Big deal. Isn’t there more to this Ghoul stuff? Oh yes, there’s more…much more. <insert rolling, evil laughter>

The cooler stuff in Ghoul deals with bolt-ons. This can be pretty tricky stuff, but there are tons of examples in the code, so don’t get scared. Part of the trickiness here, though, involves the specifics of the models you use…you need to understand the relationship between some of the stuff in Max and the corresponding Ghoul concepts. If you don’t have Max available to you, this’ll be real tough to follow, so if you can, either open up your legal copy of Max or drag a modeler over to talk to while you’re looking through the rest of this tutorial.

What In Blue Blazes is a “Bolt-on”?

For starters, a bolt-on is just a model…a model that we want to attach to another model.

To discuss bolt-ons, though,we need to talk about bolts. A bolt is something you place on a model in Max, generally referred to by modelers as a null, or occasionally as a dummy. It is simply a point in space with an associated orientation, or direction.

For an example in real life, look at a soda can. Imagine the can itself as our base model and the pulltab as a bolt-on. That little metal dingus that connects the two is a bolt, and for the sake of our discussion we’ll say that there’s one on the can and there’s one on the pulltab. That bolt exists on the can and we can consider it as having a direction, lets say it points straight up away from the center of the can.

Our pulltab is also a model…a separate model that we want to attach to our soda can model. How do we know where to attach it and how it’ll look once it’s attached? We make sure that the bolt on our pulltab is exactly where we want it to connect to our can and that the pulltab’s bolt is facing the same direction as the soda can’s bolt…straight up. This stuff we do in Max, when the models are created. Then, after we’ve exported the models from Max, in the spawn function for our object, we create a soda can model, create a pulltab model, tell Ghoul to attach the pulltab bolt to the soda can bolt, and we have successfully created a pulltab bolt-on for our soda can.

Quick Summary

  1. A base model is a model we create in Max and export as a Ghoul file.
  2. A bolt-on is a separated model we create in Max, export as a Ghoul file, and plan on attaching to our base model.
  3. A bolt, called a null when you’re working in Max, is a point on the model
  4. Bolts can be placed anywhere on a model, or even floating in space near a model, but it’s part of the model nonetheless.
  5. Bolts are placed on base models and on bolt-on models.
  6. Bolts are the common point of connection between base models and bolt-ons.

An Example From SOF

Here’s an example of a slightly more complex object than a keyboard, an object that has a bolt-on…namely, misc_generic_bench. Just so’s you know, the bench has a bottom part – the base model – and a top part, the bolt-on. You’ll occasionally see these come apart when the bench takes damage during gameplay.

The spawn function for the bench, SP_misc_generic_bench, does a lot of stuff like the keyboard did, but there’s some cool new stuff in there, too. The first thing you may notice is the following:

// Set up object to break apart when shot
 Obj_partbreaksetup(BPD_BENCH,&genericModelData[OBJ_BENCH_BOTTOM],
  genericObjBoltPartsData,genericObjBreak);

This is code you’ll find in some of the more complex furniture like tables whose legs can be shot off. For the bench, this doesn’t do much, though.

Next, the bounding box (or bbox) is set up, just like with the keyboard.

 VectorSet (ent->mins, -10, -30,-17);
 VectorSet (ent->maxs,  10,  30, 17);

After that comes some more new stuff…using a value set by the level designer to determine which skin to put on the bench model..

switch (ent->s.skinnum)
 {
 case 1:
  skinname = "bench2";
  break;
 default :
  skinname = "bench";
  break;
 }

This says that if the designer set it up to use skin 1, use the skin called “bench2”, otherwise just use the skin called “bench”.

Following the skinname stuff is a familiar call that sets up the base model for our bench, just like the keyboard did.

SimpleModelInit2(ent,&genericModelData[OBJ_BENCH_BOTTOM],skinname,NULL);

In this call to SimpleModelInit2 we’re again using an entry in the genericModelData array. For the bench, though, we’re also passing in the name of the skin we decided on earlier.

The next line calls gi.linkentity() which just tells the physics system of the game where the bench is right now.

OK, here’s the good stuff…the place where we actually do the bolting-on of the bolt-on. Ready? It’s…

 SimpleModelAddBolt(ent,genericModelData[OBJ_BENCH_BOTTOM],
"DUMMY02",genericModelData[OBJ_BENCH_TOP],"DUMMY01",skinname);

"DUMMY02",genericModelData[OBJ_BENCH_TOP],"DUMMY01",skinname);

Not all that exciting to look at, huh? It may look a lot like the SimpleModelInit2 function call where we created the base model, but let me explain what happens under the hood.

SimpleModelAddBolt takes the following parameters:

ent  -- object we&rsquo;re talking about
genericModelData[OBJ_BENCH_BOTTOM] -- the information we stored for our base model 
"DUMMY02" &ndash; Ah ha. This is the name of the bolt we placed on our base model in Max.
genericModelData[OBJ_BENCH_TOP] &ndash; info for our bolt-on model
"DUMMY01" &ndash; This is the name of the bolt we placed on our bolt-on model in Max.
skinname &ndash; This is the name of the skin we want our model to use.

Inside our function, Ghoul locates the base model for our object, creates our bolt-on object, and connects the two by gluing the point labeled "DUMMY01" to the point labeled "DUMMY02". Bam, you’re done. You just bolted something onto something else. Astoundingly simple, wouldn’t you say?

This same concept is used for dozens of other objects in the game. Take a look at misc_generic_table1. Sure, it has four legs that’re bolted onto the table surface, but all they do is call the same function we used to make the bench. Same thing for truck headlights and office chairs.

Now, here’s a thought for you. What if we want to bolt something onto a bolt-on? Heck, that’s gotta be pretty tough. Nope. It’s just as simple as the stuff we did for the bench. Turns out that when we call SimpleModelAddBolt, it returns a pointer to our bolt-on in a form called a ggBinstC, which is short for a game-ghoul bolt-on instance. Well, take this ggBinstC pointer, use it instead of a pointer to our original object, and call ComplexModelAddBolt instead of SimpleModelAddBolt. Next thing you know, you’ve bolted something onto a bolt-on. You can find several examples of this in the code, too.

REALLY FREAKING EXTRA-ADVANCED STUFF

Well, it’s not really all that advanced, but I didn’t know what else to title this section. Basically, I just wanted to mention a few other functions that you can call on objects. These are all listed in g_obj.h in the gamecpp project, but they aren’t commented terribly well aside from having fairly descriptive names.

SimpleModelTurnOnOff

If you wanted the back of the bench to just disappear when the bench gets shot, you’d do that by passing the bench-top into SimpleModelTurnOnOff.

SimpleModelRemoveObject

A model can be built in Max to have various parts, meaning they can be turned on and off by Ghoul. The helicopter, for instance, has unguided rocket launchers as separate parts. If you wanted to put a slightly less hostile helicopter in the game, you could call SimpleModelRemoveObject on the rocket launchers.

SimpleModelSetSequence

Something not discussed earlier is the idea of playing different animations on a certain model. Real simple. Just take a look at the pain function for the misc_newyork_trashcan. When the trashcan’s spawned, we tell it to hold its animation on the last frame. When it gets shot, it plays its anim of the lid flipping around and then holds the last frame. That’s a call to SimpleModelSetSequence.

SetSkin

When a TV gets shot in SOF, we change the skin on it to reflect that it’s been damaged. This is a call to SetSkin.


There you have it. That’s Manipulating Objects With Ghoul 101. Don’t forget to give your textbooks back to the school bookstore to get an unfortunately small percentage of their original price returned to you.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: