Bullet Holes
by Xavier "eXoDuS" Amado · 05/15/2002 (1:11 pm) · 39 comments
Adding Bullet Holes
In this tutorial I will ‘attempt’ to explain how the decal Manager works by setting up a simple bullet hole each time a projectile hits something.
First open up engine/game/projectile.cc and add this new include file at the top:
Then look for the function:
You will see an if statement there, after it add the following code after the if brace closes:
What this code does is very simple, or maybe not, the first line checks to see if the object the projectile collided with (notice we are in the OnCollision function) is an Interior or a Terrain object.
If it is it then checks if the decalData isn’t NULL, and if it isn’t it adds a new decal at the hitPosition using the decalData specified in the datablock as the bitmap.
Now we will add this decalData thing to our projectile, so open up projectile.h and about line 26 you will see:
After the TSThread class add:
Now in the same file look inside the ProjectileData class and under the public section add our new decalData and the decalID which we will use to keep track of the decal.
We need to initialize this values to some secure default values, so head back to projectile.cc and search for the projectileData constructor. Inside it add the following 3 lines:
Now we need to make the engine read the values of these 3 variables from the projectile datablock (the one you define in the scripts). Look for the function void ProjectileData::initPersistFields() and inside it add the following:
That addField function is telling the engine to read the value of decalData (TypeDecalDataPtr) and store it in the member variable decalData of ProjectileData.
Now search for the function ProjectileData::packData() and add this lines:
and in ProjectileData::unpackData() add this other lines:
What this does is send and receive the decalData from the server to the client (server packs, client unpacks)
We need to add 1 more line to the engine and we are done, look for the ProjectileData::preload() function and add this code:
This one checks if the decal exists before loading the projectile (preload) and if it doesn’t prints an error to the console. I don’t think this is really needed, but it was in the code I used as example and also it’s a nice way of knowing if the decal is being loaded.
Finally, recompile and … no! wait! Don’t run it just yet, you are missing something.
Open up the script of the weapon to which you want to add the bullet holes, in it add this datablock:
Make sure to change the path to the texture you want to use as the bullet hole.
Now look for the dataBlock of the projectile it self, should be like ‘datablock ProjectileData(RifleProjectile)’ and in it add this line:
decalData = BulletHoleData;
Ok now we are done, run the g… oh, you already did… ok bye, enjoy it
If you come back from testing then:
Special Thanks to Frank Bignone for helping me with a problem I had while coding this.
Also thanks to everyone that hangs out on IRC everyday (this is intended to get more ppl on IRC)
If you are still reading might be because you had problems, if you do feel free to email me.
FAQ:
Q: Can I add decals to the player?
A: Yes you can but, they are attached to the bounding box of the player… so it looks kinda funny, if you want to try if by yourself remove the line ‘if (hitObject->getType() & (InteriorObjectType|TerrainObjectType))’ and its closing brace, so decals will be attached to every surface and not only to Interiors or Terrains.
Q: Is there a way to prevent the decal manager from fading out the bulletholes after a couple of seconds (make them persistant)?
A: Yes, you can set the decal timeout in the prefs.cs file, thanks Stefan.
Q: Will there be any more questions here?
A: If they are asked to me and I think they are important I’ll add them right here.
In this tutorial I will ‘attempt’ to explain how the decal Manager works by setting up a simple bullet hole each time a projectile hits something.
First open up engine/game/projectile.cc and add this new include file at the top:
#include "sim/decalManager.h"
Then look for the function:
void Projectile::onCollision(const Point3F& hitPosition,
const Point3F& hitNormal,
SceneObject* hitObject)You will see an if statement there, after it add the following code after the if brace closes:
if(isGhost())
{
if (hitObject->getType() & (InteriorObjectType|TerrainObjectType))
{
if (mDataBlock->decalData != NULL)
{
if(mSceneManager->getCurrentDecalManager())
mSceneManager->getCurrentDecalManager()->addDecal(hitPosition, hitNormal, mDataBlock->decalData);
}
}
}What this code does is very simple, or maybe not, the first line checks to see if the object the projectile collided with (notice we are in the OnCollision function) is an Interior or a Terrain object.
If it is it then checks if the decalData isn’t NULL, and if it isn’t it adds a new decal at the hitPosition using the decalData specified in the datablock as the bitmap.
Now we will add this decalData thing to our projectile, so open up projectile.h and about line 26 you will see:
class ExplosionData; class ShapeBase; class TSShapeInstance; class TSThread;
After the TSThread class add:
class DecalData;
Now in the same file look inside the ProjectileData class and under the public section add our new decalData and the decalID which we will use to keep track of the decal.
DecalData *decalData; S32 decalID;
We need to initialize this values to some secure default values, so head back to projectile.cc and search for the projectileData constructor. Inside it add the following 3 lines:
decalData = NULL; decalID = 0;
Now we need to make the engine read the values of these 3 variables from the projectile datablock (the one you define in the scripts). Look for the function void ProjectileData::initPersistFields() and inside it add the following:
addField("decalData", TypeDecalDataPtr, Offset(decalData, ProjectileData));That addField function is telling the engine to read the value of decalData (TypeDecalDataPtr) and store it in the member variable decalData of ProjectileData.
Now search for the function ProjectileData::packData() and add this lines:
if( stream->writeFlag( decalData ) )
{
stream->writeRangedU32( decalData->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
}and in ProjectileData::unpackData() add this other lines:
if( stream->readFlag() )
{
decalID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
}What this does is send and receive the decalData from the server to the client (server packs, client unpacks)
We need to add 1 more line to the engine and we are done, look for the ProjectileData::preload() function and add this code:
if (!decalData && decalID != 0 )
if (!Sim::findObject(decalID, decalData))
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload Invalid packet, bad datablockId(decalData): 0x%x", decalID);This one checks if the decal exists before loading the projectile (preload) and if it doesn’t prints an error to the console. I don’t think this is really needed, but it was in the code I used as example and also it’s a nice way of knowing if the decal is being loaded.
Finally, recompile and … no! wait! Don’t run it just yet, you are missing something.
Open up the script of the weapon to which you want to add the bullet holes, in it add this datablock:
datablock DecalData(BulletHoleData)
{
sizeX = 0.125;
sizeY = 0.125;
textureName = "~/data/special/weapons/bullethole";
};Make sure to change the path to the texture you want to use as the bullet hole.
Now look for the dataBlock of the projectile it self, should be like ‘datablock ProjectileData(RifleProjectile)’ and in it add this line:
decalData = BulletHoleData;
Ok now we are done, run the g… oh, you already did… ok bye, enjoy it
If you come back from testing then:
Special Thanks to Frank Bignone for helping me with a problem I had while coding this.
Also thanks to everyone that hangs out on IRC everyday (this is intended to get more ppl on IRC)
If you are still reading might be because you had problems, if you do feel free to email me.
FAQ:
Q: Can I add decals to the player?
A: Yes you can but, they are attached to the bounding box of the player… so it looks kinda funny, if you want to try if by yourself remove the line ‘if (hitObject->getType() & (InteriorObjectType|TerrainObjectType))’ and its closing brace, so decals will be attached to every surface and not only to Interiors or Terrains.
Q: Is there a way to prevent the decal manager from fading out the bulletholes after a couple of seconds (make them persistant)?
A: Yes, you can set the decal timeout in the prefs.cs file, thanks Stefan.
Q: Will there be any more questions here?
A: If they are asked to me and I think they are important I’ll add them right here.
#2
05/15/2002 (10:46 am)
Tutorial updated
#3
Good job Xavier!
05/15/2002 (1:48 pm)
I've already got this in, but this is one tutorial that will definately get a lot of use by people in the community.Good job Xavier!
#4
05/15/2002 (3:32 pm)
Thanks a lot Matt! :)
#5
05/16/2002 (2:28 pm)
Nice Tut thanks Xavier.
#6
05/17/2002 (1:49 am)
is there anyway to stop it from showing them on the bounding box? and on the skin?
#7
Maybe you should draw the decal relative to the players origin.
05/17/2002 (9:08 am)
@edward, the decal is positioned in the coordinates that the projectile returns. The projectile is considered to have hit the player when it's ray crosses the bounding box. So to be able to render the decals on the player itself you would have to change the projectile collision code, ie you could do this using hitboxes, josh albrecht did a good tutorial on that and new version should be out soon. But...(i'm not sure about im about to say) the decal is drawn at specified coordinates.. relative to the world, so if the player moves the decal doesnt :\Maybe you should draw the decal relative to the players origin.
#8
and is it possible to change the alpha values of the imact site to create the illusion of an actual hole?
05/19/2002 (6:13 pm)
how would i get it to change the decal image based on what the projectile hits? ie if it hits cement it leaves a hole and if it hits metal it leaves a dent.and is it possible to change the alpha values of the imact site to create the illusion of an actual hole?
#9
I tried out your code this last weekend.
The decals work on the unmodified 1.1.1 demo. It seems to have conflicts with sound datablocks though. If I add the sound datablocks for rifle fire, dry fire, explosion etc the demo won't load. It gets stuck on the "loading datablocks" section. If I comment out the decal datablock sound works (as well as it always has anyway), if I comment out the sound datablocks the decal stuff works. I couldn't see a reason fo a conflict but there must be. I also noticed that if two decals slightly overlap they sorta "flicker" back and forth.
Have you or anyone else seen either of these problems?
05/20/2002 (5:21 am)
Xavier,I tried out your code this last weekend.
The decals work on the unmodified 1.1.1 demo. It seems to have conflicts with sound datablocks though. If I add the sound datablocks for rifle fire, dry fire, explosion etc the demo won't load. It gets stuck on the "loading datablocks" section. If I comment out the decal datablock sound works (as well as it always has anyway), if I comment out the sound datablocks the decal stuff works. I couldn't see a reason fo a conflict but there must be. I also noticed that if two decals slightly overlap they sorta "flicker" back and forth.
Have you or anyone else seen either of these problems?
#10
Look at how it works, and get the "properties" of the object it hits. Play the sound, decal, and particle depending on what it finds as the properties.
I believe CrimeWars (Is that right? I haven't seen that for a while) had different sounds based on the material hit.
05/20/2002 (5:52 pm)
Anon, same as the footstep sounds.Look at how it works, and get the "properties" of the object it hits. Play the sound, decal, and particle depending on what it finds as the properties.
I believe CrimeWars (Is that right? I haven't seen that for a while) had different sounds based on the material hit.
#11
@Sabre, i dont see why there should be conflicts beetween the decal manager and sounds, and yes i know the flicker, but that's a decal manager problem , i didnt do any drawing i just called the decal manager in my code.
05/20/2002 (6:48 pm)
@Matt, it was crimeforce :)@Sabre, i dont see why there should be conflicts beetween the decal manager and sounds, and yes i know the flicker, but that's a decal manager problem , i didnt do any drawing i just called the decal manager in my code.
#12
Your tutorial was easy to understand :)
Added note:
I got the bullet holes to work with sound :), but it only works if one audio profile datablock is set up in the rifle.cs file. If I add 2 or more audio datablocks..nada. ???
This might just be another issue with the overall current sound problems.
05/21/2002 (4:51 am)
Yeah, I don't get it either. I couldn't see why there would be any conflict. Might have to test it out some more. I did try it on a "fresh" 1.1.1 and the HEAD version and got the same thing happening. Good until the sound datablocks were added. Maybe that has to do with the decal manager too. I'll be sure to post something if I find out a reason for what's going on. Your tutorial was easy to understand :)
Added note:
I got the bullet holes to work with sound :), but it only works if one audio profile datablock is set up in the rifle.cs file. If I add 2 or more audio datablocks..nada. ???
This might just be another issue with the overall current sound problems.
#13
06/07/2002 (5:17 am)
@Sabre, it's a weird problem. Good Luck solving it :\
#14
Good news! I got the damn thing finally to work with as many sound datablocks as I want.
I had to move the location of some of the added code for some reason, but now it works fine :)
06/18/2002 (7:17 am)
Xavier,Good news! I got the damn thing finally to work with as many sound datablocks as I want.
I had to move the location of some of the added code for some reason, but now it works fine :)
#15
06/18/2002 (12:07 pm)
Bug found you cannot see the bullet hole if you are conecting to the server, only host can. This is similar to a bug found with mission bounds area. Need to investigate this
#16
06/22/2002 (7:02 pm)
can't see why that would happen
#17
I was playing around with some network stuff this weekend and found the same thing. Decals only appear on the host machine. I couldn't see what the problem was either.
Any luck with it?
-Sabrecyd
07/01/2002 (5:23 am)
Anthony, I was playing around with some network stuff this weekend and found the same thing. Decals only appear on the host machine. I couldn't see what the problem was either.
Any luck with it?
-Sabrecyd
#18
07/04/2002 (5:48 pm)
I've got the same problem here... not sure why.
#19
In projectile.cc, in the Projectile::onCollision method, I moved the decal-ing code around, plus changed the scene manager being used.
I started from here:
Using gClientSceneGraph is what caught my eye. Made me wonder if the decal was, therefore, *only* being added to the *client*...and since the only client in this case would be the hosting player, that's why they were only ones who could see the decal.
After a bit of digging, I changed the above to this:
It still didn't work...but then, I noticed the final piece of the puzzle, just above that:
The code was only executing when server-side copy of the projectile had a collision.
To solve that, I moved the decal-ing code to the beginning of the Projectile::onCollision method. I also added a check to make sure that the hitObject variable wasn't NULL to prevent that from being a problem.
So, the final Projectile::onCollision:
I tested it with a friend of mine, and it worked fine.
Hope that helps.
-David
Samu Games
07/08/2002 (11:53 am)
I think I have solved the problem with the projectile decals not showing up for non-local players.In projectile.cc, in the Projectile::onCollision method, I moved the decal-ing code around, plus changed the scene manager being used.
I started from here:
if(gClientSceneGraph->getCurrentDecalManager()) gClientSceneGraph->getCurrentDecalManager()->addDecal(hitPosition, hitNormal, mDataBlock->decalData);
Using gClientSceneGraph is what caught my eye. Made me wonder if the decal was, therefore, *only* being added to the *client*...and since the only client in this case would be the hosting player, that's why they were only ones who could see the decal.
After a bit of digging, I changed the above to this:
if(mSceneManager->getCurrentDecalManager()) mSceneManager->getCurrentDecalManager()->addDecal(hitPosition, hitNormal, mDataBlock->decalData);
It still didn't work...but then, I noticed the final piece of the puzzle, just above that:
if (!isClientObject() && hitObject != NULL)
{
...The code was only executing when server-side copy of the projectile had a collision.
To solve that, I moved the decal-ing code to the beginning of the Projectile::onCollision method. I also added a check to make sure that the hitObject variable wasn't NULL to prevent that from being a problem.
So, the final Projectile::onCollision:
void Projectile::onCollision(const Point3F& hitPosition,
const Point3F& hitNormal,
SceneObject* hitObject)
{
if (hitObject != NULL)
{
if (hitObject->getType() & (InteriorObjectType|TerrainObjectType))
{
if (mDataBlock->decalData != NULL)
{
if(mSceneManager->getCurrentDecalManager())
mSceneManager->getCurrentDecalManager()->addDecal(hitPosition, hitNormal, mDataBlock->decalData);
}
}
}
if (!isClientObject() && hitObject != NULL) {
char *posArg = Con::getArgBuffer(64);
char *normalArg = Con::getArgBuffer(64);
dSprintf(posArg, 64, "%f %f %f", hitPosition.x, hitPosition.y, hitPosition.z);
dSprintf(normalArg, 64, "%f %f %f", hitNormal.x, hitNormal.y, hitNormal.z);
Con::executef(mDataBlock, 6, "onCollision",
scriptThis(),
Con::getIntArg(hitObject->getId()),
Con::getFloatArg(mFadeValue),
posArg,
normalArg);
}
}I tested it with a friend of mine, and it worked fine.
Hope that helps.
-David
Samu Games
#20
I'll give it a try tonight on my LAN setup. Sounds like it worked for you - Good deal. Having the added bullet hole effect is great.
-Sabrecyd
07/08/2002 (12:05 pm)
David,I'll give it a try tonight on my LAN setup. Sounds like it worked for you - Good deal. Having the added bullet hole effect is great.
-Sabrecyd

Associate Stefan Beffy Moises
Two little "corrections", though...
the functions ProjectileData::packUpdate()
and ProjectileData::unpackUpdate() don't exist, they're called ProjectileData::packData() and ProjectileData::unpackData() - guess just a typo on your side...
One question: is there a way to prevent the decal manager from fading out the bulletholes after a couple of seconds (make them persistant)?
EDIT: Found it, you can set the decal timeout in prefs.cs, so I'll just set it to a couple of minutes/hours, hehe ;-)
Thanks for the code!!