Torque3D Tracer Rounds
by Andrew Edmonds · 01/15/2010 (11:55 am) · 8 comments
I'd had 'tracer rounds' written on my to-do list for so long and after searching a lot, I still couldn't find anything that would fit the bill so I went ahead and wrote it myself. WARNING: It's not entirely realistic. In reality, a tracer round would be placed in a clip at (for example) every 5th position. This code makes an approximation of that - it allows you to set a percentage of your rounds that you wish to be tracers and uses a random number to decide whether to fire a tracer or not (so if you set a percentage of 20 in the datablock, roughly 1 on 5 rounds will be a tracer round). If you really want to do it properly, you could probably extend what I've done here. If that's enough for you, then let's continue!
EDIT: 'The People' demanded video, and who am I to argue? This shows the effect in action, but the muzzle velocity has been slowed right down (to 5) to show the effect. Also, the light has been brightened up massively - again to show the effect. In reality, our muzzle velocity is somewhere in the region of 400 for this weapon so it's a blink-and-you'll-miss-it effect.
Fields
The following fields are added to the ProjectileData datablock:
Source code changes
There are quite a few small source code changes so I apologise if it is a bit hard to read. This was done with a stock 1.1 Alpha codebase.
projectile.h
At around line 77, after
Add
At around line 105, after
Add
At around line 179, after
Add
That's it for projectile.h
projectile.cpp
At around line 55, after
Add
At around line 125, after
Add
At around line 222, after
Add
At around line 242, after
Add
At around line 268, after
Add
At around line 308, after
Add
At around line 338, after
Add
At around line 376, after
Add
At around line 571, inside bool Projectile::onAdd()
After
Change:
To:
At around line 687, change the entire void Projectile::submitLights( LightManager *lm, bool staticLighting ) function to
At around line 1318, under
Add
That's it for the source changes - recompile the engine and move over to your weapon.cs script file.
Script change example
I am using this with an XM8 weapon (from the modmaker weapon pack). I added a new LightDescription for my tracer light source and added the fields to my ProjectileData datablock as follows:
I have uploaded an example tracer (programmer art!) here.
EDIT: 'The People' demanded video, and who am I to argue? This shows the effect in action, but the muzzle velocity has been slowed right down (to 5) to show the effect. Also, the light has been brightened up massively - again to show the effect. In reality, our muzzle velocity is somewhere in the region of 400 for this weapon so it's a blink-and-you'll-miss-it effect.
Fields
The following fields are added to the ProjectileData datablock:
- projectileTracerShapeName : The shape file for your tracer projectile.
- tracerLightDesc : The LightDescription for your tracer. This allows you to have regular projectiles lit one way (or not at all) and your tracer projectiles lit another way (or not at all).
- tracerPercent : The percentage of your rounds that you wish to be tracers
Source code changes
There are quite a few small source code changes so I apologise if it is a bit hard to read. This was done with a stock 1.1 Alpha codebase.
projectile.h
At around line 77, after
S32 armingDelay; // the values are converted on initialization with S32 fadeDelay; // the IRangeValidatorScaled field validator
Add
// tracerRound >>> const char* projectileTracerShapeName; F32 tracerPercent; LightDescription *tracerLightDesc; S32 tracerLightDescId; // tracerRound >>>
At around line 105, after
// variables set on preload: Resource<TSShape> projectileShape;
Add
// tracerRound >>> Resource<TSShape> projectileTracerShape; // tracerRound <<<
At around line 179, after
// Rendering related variables TSShapeInstance* mProjectileShape; TSThread* mActivateThread; TSThread* mMaintainThread;
Add
// tracerRound >>> bool mIsTracer; // tracerRound <<<
That's it for projectile.h
projectile.cpp
At around line 55, after
ProjectileData::ProjectileData()
{
projectileShapeName = NULL;Add
// tracerRound >>> projectileTracerShapeName = NULL; tracerPercent = 0; tracerLightDesc = NULL; tracerLightDescId = 0; // tracerRound <<<
At around line 125, after
addNamedField(projectileShapeName, TypeFilename, ProjectileData); addNamedField(scale, TypePoint3F, ProjectileData);
Add
// tracerRound >>> addNamedField(projectileTracerShapeName, TypeFilename, ProjectileData); addNamedField(tracerPercent, TypeF32, ProjectileData); addNamedField(tracerLightDesc, TypeLightDescriptionPtr, ProjectileData); // tracerRound <<<
At around line 222, after
if (!lightDesc && lightDescId != 0)
if (Sim::findObject(lightDescId, lightDesc) == false)
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockid(lightDesc): %d", lightDescId);Add
// tracerRound >>> if (!tracerLightDesc && tracerLightDescId != 0) if (Sim::findObject(tracerLightDescId, tracerLightDesc) == false) Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockid(tracerLightDesc): %d", tracerLightDescId); // tracerRound <<<
At around line 242, after
}
activateSeq = projectileShape->findSequence("activate");
maintainSeq = projectileShape->findSequence("maintain");
}Add
// tracerRound >>>
if (projectileTracerShapeName && projectileTracerShapeName[0] != '')
{
projectileTracerShape = ResourceManager::get().load(projectileTracerShapeName);
if (bool(projectileTracerShape) == false)
{
errorStr = String::ToString("ProjectileData::load: Couldn't load shape "%s"", projectileTracerShapeName);
return false;
}
}
// tracerRound <<<At around line 268, after
Parent::packData(stream); stream->writeString(projectileShapeName);
Add
// tracerRound >>> stream->writeString(projectileTracerShapeName); // tracerRound <<<
At around line 308, after
if ( stream->writeFlag(lightDesc != NULL))
stream->writeRangedU32(lightDesc->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);Add
// tracerRound >>> if ( stream->writeFlag(tracerLightDesc != NULL)) stream->writeRangedU32(tracerLightDesc->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); // tracerRound <<<
At around line 338, after
Parent::unpackData(stream); projectileShapeName = stream->readSTString();
Add
// tracerRound >>> projectileTracerShapeName = stream->readSTString(); // tracerRound <<<
At around line 376, after
if (stream->readFlag()) lightDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
Add
// tracerRound >>> if (stream->readFlag()) tracerLightDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); // tracerRound <<<
At around line 571, inside bool Projectile::onAdd()
After
// If we're on the server, we need to inherit some of our parent's velocity // mCurrTick = 0; } else
Change:
if (bool(mDataBlock->projectileShape))
{
mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, isClientObject());
if (mDataBlock->activateSeq != -1)
{
mActivateThread = mProjectileShape->addThread();
mProjectileShape->setTimeScale(mActivateThread, 1);
mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0);
}
}To:
if (bool(mDataBlock->projectileShape))
{
// tracerRound >>>
mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, isClientObject());
S32 random_number = mRandI(0, 100);
F32 theTracerPercent = mDataBlock->tracerPercent;
if (theTracerPercent > 100)
theTracerPercent = 100;
if (theTracerPercent < 0)
theTracerPercent = 0;
if (random_number > 100-theTracerPercent) {
// use tracer if one is defined in the datablock
if (mDataBlock->projectileTracerShape) {
mProjectileShape = new TSShapeInstance(mDataBlock->projectileTracerShape, isClientObject());
mIsTracer = true;
} else {
mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, isClientObject());
mIsTracer = false;
}
} else {
// use normal projectile
mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, isClientObject());
mIsTracer = false;
}
// tracerRound <<<
if (mDataBlock->activateSeq != -1)
{
mActivateThread = mProjectileShape->addThread();
mProjectileShape->setTimeScale(mActivateThread, 1);
mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0);
}
}At around line 687, change the entire void Projectile::submitLights( LightManager *lm, bool staticLighting ) function to
void Projectile::submitLights( LightManager *lm, bool staticLighting )
{
// tracerRound >>>
if (staticLighting || mHidden || (!mDataBlock->lightDesc && !mDataBlock->tracerLightDesc))
return;
if (this->mIsTracer) {
if (mDataBlock->tracerLightDesc) {
mDataBlock->tracerLightDesc->submitLight( &mLightState, getRenderTransform(), lm, this);
}
} else {
if (mDataBlock->lightDesc) {
mDataBlock->lightDesc->submitLight( &mLightState, getRenderTransform(), lm, this );
}
}
// tracerRound <<<
}At around line 1318, under
if (state->isObjectRendered(this))
{
if ( mDataBlock->lightDesc )
{
mDataBlock->lightDesc->prepRender( state, &mLightState, getRenderTransform() );
}Add
// tracerRound >>>
if (mDataBlock->tracerLightDesc)
{
mDataBlock->tracerLightDesc->prepRender(state, &mLightState, getRenderTransform());
}
// tracerRound <<<That's it for the source changes - recompile the engine and move over to your weapon.cs script file.
Script change example
I am using this with an XM8 weapon (from the modmaker weapon pack). I added a new LightDescription for my tracer light source and added the fields to my ProjectileData datablock as follows:
datablock LightDescription(xm8TracerLightDesc)
{
range = 5.0;
color = "1 1 0";
brightness = 20.0;
};
datablock ProjectileData( xm8Projectile )
{
projectileShapeName = "art/shapes/weapons/xm8/ammo/5_56.dts";
directDamage = 30;
radiusDamage = 30;
damageRadius = 5;
areaImpulse = 2500;
explosion = BulletDirtExplosion;
waterExplosion = BulletWaterExplosion;
muzzleVelocity = 400;
armingDelay = 0;
lifetime = 4992;
fadeDelay = 4992;
isBallistic = false;
gravityMod = 0.5;
projectileTracerShapeName = "art/shapes/weapons/xm8/tracer.dts"; // The tracer .dts
tracerPercent = 20; // approx 1 in 5 rounds
//lightDesc = xm8BulletLightDesc; // no light source on normal bullets
tracerLightDesc = xm8TracerLightDesc; // the tracer light source
};I have uploaded an example tracer (programmer art!) here.
About the author
Formed in 2005, EiKON Games is an indie games development project based in the UK working on the tactical first person shooter "Epoch: Incursion". See the Join Us or Contact Us pages at http://www.eikon-games.com/
#2
01/15/2010 (1:30 pm)
Well, OK. But only because the people demanded it... :o)
#3
01/15/2010 (2:45 pm)
hehe nice work
#4
but any word about multiplayer? I tried on multiplayer locally (on one PC) and it didn't work, but it might just be because i ran it on one PC.
01/16/2010 (12:09 pm)
This is just what i needed! Thanks for sharing!but any word about multiplayer? I tried on multiplayer locally (on one PC) and it didn't work, but it might just be because i ran it on one PC.
#5
In short - more testing required! I should be able to test in the next week or so.
01/16/2010 (1:10 pm)
I tested multiplayer on a lan between my dev pc and my laptop. The problem being my laptop really isn't up to running torque3d so it was struggling. I need to test it properly with a couple of decent pc's. However - that said, I was seeing some tracer fire on my laptop when I was firing from my desktop pc...In short - more testing required! I should be able to test in the next week or so.
#6
from this
At around line 242, after
to this
At around line 242, after
// tracerRound >>>
if (projectileTracerShapeName && projectileTracerShapeName[0] != '\0')
{
projectileTracerShape = ResourceManager::get().load(projectileTracerShapeName);
if (bool(projectileTracerShape) == false)
{
errorStr = String::ToString("ProjectileData::load: Couldn't load shape \"%s\"", projectileTracerShapeName);
return false;
}
}
01/18/2010 (10:43 am)
Looks like the site is stripping the slashes from the code... Giving some compile errors about empty variables and whatnot. Hopefully the code won't be stripped again in this. This rocks by the way, nicely done. It certainly looks like tracer rounds; although, like you said it's not 100% accurate as far as timing is concerned. That's not a huge issue though. Thank you for the work!from this
At around line 242, after
// tracerRound >>>
if (projectileTracerShapeName && projectileTracerShapeName[0] != '')
{
projectileTracerShape = ResourceManager::get().load(projectileTracerShapeName);
if (bool(projectileTracerShape) == false)
{
errorStr = String::ToString("ProjectileData::load: Couldn't load shape "%s"", projectileTracerShapeName);
return false;
}
}
// tracerRound <<<to this
At around line 242, after
// tracerRound >>>
if (projectileTracerShapeName && projectileTracerShapeName[0] != '\0')
{
projectileTracerShape = ResourceManager::get().load(projectileTracerShapeName);
if (bool(projectileTracerShape) == false)
{
errorStr = String::ToString("ProjectileData::load: Couldn't load shape \"%s\"", projectileTracerShapeName);
return false;
}
}
#7
04/18/2010 (7:55 am)
Has anyone been able to get this to show up on both the client and server computers? When I was testing my game with a friend of mine the tracers showed up find on my machine (the host server) but never on his. When he hosted the server they showed up for him but not for me.
#8
'mHidden' replace to 'isHidden()' on T3D 1.1...
08/19/2010 (5:35 am)
if (staticLighting || mHidden || (!mDataBlock->lightDesc && !mDataBlock->tracerLightDesc))
'mHidden' replace to 'isHidden()' on T3D 1.1...

Associate Steve Acaster
[YorkshireRifles.com]
[edit]The People are sated!
Good idea to slow it down and amplify the effect for demonstration purposes.