RTSUnit Damage Particle Emitters
by BillF · in RTS Starter Kit · 03/16/2008 (10:15 am) · 11 replies
This resource adds progressive damage particles (puffs of smoke and fire) to an RTSUnit that will make a unit seem like its smoking or on fire due to damage. Its most useful for buildings and vehicle type units, although if you wanted to make a walking unit smoke or be on fire I guess it might come in handy :)
The code to do it comes directly from the standard engine's Vehicle class. I modified it a bit to make it....well work first, and I added a few other flags to have more control on the behavior. First, here are some screen shots



The basic idea is a particle emmiter is a point, in this case on a unit, that spawns particles defined by size, color, speed, lifetime, and a bunch of other attributes. The way the damage particles work is there are predefined thresholds on each level of damage (say 25%, 50%, 75%) and the damage smoke/fire/particle emmiter is registered for one particular level of damage. I expanded the standard code to allow the damage levels to either show only the active damage level, or all at the same time.
The code to do it comes directly from the standard engine's Vehicle class. I modified it a bit to make it....well work first, and I added a few other flags to have more control on the behavior. First, here are some screen shots



The basic idea is a particle emmiter is a point, in this case on a unit, that spawns particles defined by size, color, speed, lifetime, and a bunch of other attributes. The way the damage particles work is there are predefined thresholds on each level of damage (say 25%, 50%, 75%) and the damage smoke/fire/particle emmiter is registered for one particular level of damage. I expanded the standard code to allow the damage levels to either show only the active damage level, or all at the same time.
#2
add the top of the file add
NOTE: make sure you have the image file "~/data/shapes/particles/smoke"; in the correct location, if you don't have it hunt around in the demo code for FPS and others and move it into the right spot
Next at the bottom of your datablock RTSUnitData definition after
add
NOTE: The damageLevelTolerance floats define the percentage levels at which each emitter will show. I made the first one 15% just for debugging purposes, would be more fitting to be .25.
And that's it! Feel free to play with the settings and colors to fit your needs. This code was used to generate the screen shots shown above which were specific to the type of smoke/fire I wanted.
03/16/2008 (10:16 am)
Next, in an RTSUnit of your choice in \server\scripts\avatars add the following to a units .cc fileadd the top of the file add
datablock ParticleData(RTSUnitLightDamageParticle)
{
textureName = "~/data/shapes/particles/smoke";
dragCoefficient = 1.0;
gravityCoefficient = -0.01;
inheritedVelFactor = 0.1;
constantAcceleration = 0.0;
lifetimeMS = 1000;
lifetimeVarianceMS = 500;
useInvAlpha = true;
spinRandomMin = -90.0;
spinRandomMax = 500.0;
colors[0] = "0.20 0.20 0.20 0.3";
colors[1] = "0.1 0.1 0.1 0.3";
sizes[0] = 1.0;
sizes[1] = 1.5;
};
datablock ParticleEmitterData(RTSUnitLightDamageEmitter)
{
ejectionPeriodMS = 10;
periodVarianceMS = 0;
ejectionVelocity = 2;
velocityVariance = 2.0;
ejectionOffset = 0.0;
thetaMin = 25;
thetaMax = 35;
phiReferenceVel = 0;
phiVariance = 90;
overrideAdvances = false;
particles = "RTSUnitLightDamageParticle";
};
datablock ParticleData(RTSUnitMediumDamageParticle)
{
textureName = "~/data/shapes/particles/smoke";
dragCoefficient = 1.0;
gravityCoefficient = -0.01;
inheritedVelFactor = 0.3;
constantAcceleration = 0.0;
lifetimeMS = 5000;
lifetimeVarianceMS = 1000;
useInvAlpha = true;
spinRandomMin = -90.0;
spinRandomMax = 500.0;
colors[0] = "0.20 0.20 0.20 0.3";
colors[1] = "0.1 0.1 0.1 0.3";
colors[2] = "0.33 0.33 0.33 0.05";
colors[3] = "0 0 0 0";
sizes[0] = 2.0;
sizes[1] = 2.5;
sizes[2] = 3.25;
sizes[3] = 4.0;
};
datablock ParticleEmitterData(RTSUnitMediumDamageEmitter)
{
ejectionPeriodMS = 10;
periodVarianceMS = 0;
ejectionVelocity = 5;
velocityVariance = 2.0;
ejectionOffset = 0.4;
thetaMin = 0;
thetaMax = 45;
phiReferenceVel = 0;
phiVariance = 180;
overrideAdvances = false;
particles = "RTSUnitMediumDamageParticle";
};
datablock ParticleData(RTSUnitHeavyDamageParticle)
{
textureName = "~/data/shapes/particles/smoke";
dragCoefficient = 0.0;
gravityCoefficient = -0.35;
inheritedVelFactor = 0.1;
constantAcceleration = 0.0;
lifetimeMS = 580;
lifetimeVarianceMS = 150;
useInvAlpha = false;
colors[0] = "0.8 0.6 0.0 0.1";
colors[1] = "0.8 0.65 0.0 0.1";
colors[2] = "0.0 0.0 0.0 0.0";
sizes[0] = 1.0;
sizes[1] = 2.0;
sizes[2] = 4.0;
times[0] = 0.1;
times[1] = 0.4;
times[2] = 1.0;
};
datablock ParticleEmitterData(RTSUnitHeavyDamageEmitter)
{
ejectionPeriodMS = 15;
periodVarianceMS = 5;
ejectionVelocity = 0.35;
velocityVariance = 0.20;
ejectionOffset = 0.0;
thetaMin = 25;
thetaMax = 35;
phiReferenceVel = 0;
particles = "RTSUnitHeavyDamageParticle";
};NOTE: make sure you have the image file "~/data/shapes/particles/smoke"; in the correct location, if you don't have it hunt around in the demo code for FPS and others and move it into the right spot
Next at the bottom of your datablock RTSUnitData definition after
boundingBox = "2.0 2.0 2.0";
add
damageEmitter[0] = RTSUnitLightDamageEmitter; damageEmitter[1] = RTSUnitMediumDamageEmitter; damageEmitter[2] = RTSUnitHeavyDamageEmitter; damageEmitterOffset[0] = "0.0 0.0 2.0"; damageEmitterOffset[1] = "0.0 0.0 2.5"; damageEmitterOffset[2] = "0.0 0.0 1.0"; damageLevelTolerance[0] = 0.15; damageLevelTolerance[1] = 0.50; damageLevelTolerance[2] = 0.75; numDamageEmitters = 3; ShowAllDamageEmmiters = true;
NOTE: The damageLevelTolerance floats define the percentage levels at which each emitter will show. I made the first one 15% just for debugging purposes, would be more fitting to be .25.
And that's it! Feel free to play with the settings and colors to fit your needs. This code was used to generate the screen shots shown above which were specific to the type of smoke/fire I wanted.
#4
04/03/2008 (6:20 pm)
Would these same code modifications work for buildings too if added to RTSBuilding.h and RTSBuilding.cc and added the script changes.
#5
If I get a chance I'll test it with a building in the next few days.
04/03/2008 (7:04 pm)
I haven't tested it with buildings yet, but it should work when you add the changes to RTSUnit.h/cc, since it is the base class of RTSBuilding - so its handled already. You'll just need to add the changes to the building's cs file as shown, and ...it should work. Famous last words :)If I get a chance I'll test it with a building in the next few days.
#6
Fatal: (c:\rts\engine\core\bitstream.cc @ 246) Out of range read
It was coming from this in bitstream.cc:
04/05/2008 (7:13 pm)
I'm getting this to compile but when I go to run it crashes while loading. The last thing the log said was,Fatal: (c:\rts\engine\core\bitstream.cc @ 246) Out of range read
It was coming from this in bitstream.cc:
inline bool BitStream::readFlag()
{
if(bitNum > maxReadBitNum)
{
error = true;
AssertFatal(false, "Out of range read");
return false;
}
S32 mask = 1 << (bitNum & 0x7);
bool ret = (*(dataPtr + (bitNum >> 3)) & mask) != 0;
bitNum++;
return ret;
}
#7
Does every read in unpackData match every write in packData? I've seen things happen like this where if one readFlag is in the wrong position it will cause the assert you're seeing. The reads and writes happen squentially so they need to be in the exact same positions.
If everything looks correct in your code I'll apply these steps to a clean build and double check again.
04/05/2008 (8:05 pm)
Jonathon, I double checked the code posted with my changes and everything looks correct. Does every read in unpackData match every write in packData? I've seen things happen like this where if one readFlag is in the wrong position it will cause the assert you're seeing. The reads and writes happen squentially so they need to be in the exact same positions.
If everything looks correct in your code I'll apply these steps to a clean build and double check again.
#8
04/06/2008 (9:46 am)
Yes you were right. I was missing a write in packData. Everything works now.
#9
04/06/2008 (11:00 am)
Cool, I was about to try this to give a hand here, but glad to see this works. Im thinking on open a TDN section called "Code Snipets" with all this awesome resources.
#10
04/06/2008 (11:36 am)
I got it to work with the RTSBuilding with no changes. Just make sure to add a maxDamage field.
#11
04/06/2008 (12:53 pm)
Great! Glad it works.
Torque Owner BillF
In RTSUnit.h add
after
add
after
add
enum RTSUnitEmitterConsts { VC_MAX_NUM_DAMAGE_EMITTERS = 5, }; ParticleEmitterData * damageEmitterList[ VC_MAX_NUM_DAMAGE_EMITTERS ]; Point3F damageEmitterOffset[VC_MAX_NUM_DAMAGE_EMITTERS]; S32 damageEmitterIDList[VC_MAX_NUM_DAMAGE_EMITTERS]; F32 damageLevelTolerance[VC_MAX_NUM_DAMAGE_EMITTERS]; F32 numDamageEmitters; bool bShowAllDamageEmmiters;NOTE: This puts a limit on the number of damage emitters on a unit to 5. Feel free to change if you feel you need more.
after
add
after
add
now in RTSUnit.cc
after
add
in constructor, after
add
in packData after
add
stream->write( numDamageEmitters ); for (int i = 0; i < numDamageEmitters; i++) { if( stream->writeFlag( damageEmitterList[i] != NULL ) ) { stream->writeRangedU32( damageEmitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); } } for (int j = 0; j < numDamageEmitters; j++) { stream->write( damageEmitterOffset[j].x ); stream->write( damageEmitterOffset[j].y ); stream->write( damageEmitterOffset[j].z ); } for (int k = 0; k < numDamageEmitters; k++) { stream->write( damageLevelTolerance[k] ); } stream->writeFlag(bShowAllDamageEmmiters);in unpackData after
add
stream->read( &numDamageEmitters ); for (int i = 0; i < numDamageEmitters; i++) { if( stream->readFlag() ) { damageEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); } } for( int j=0; j<numDamageEmitters; j++ ) { stream->read( &damageEmitterOffset[j].x ); stream->read( &damageEmitterOffset[j].y ); stream->read( &damageEmitterOffset[j].z ); } for( int k=0; k<numDamageEmitters; k++ ) { stream->read( &damageLevelTolerance[k] ); } bShowAllDamageEmmiters = stream->readFlag();in initPersistFields after
addField("doLookAnimation", TypeBool, Offset(mDoLookAnimation, RTSUnitData));add
addField("damageEmitter", TypeParticleEmitterDataPtr, Offset(damageEmitterList, RTSUnitData), VC_MAX_NUM_DAMAGE_EMITTERS); addField("damageEmitterOffset", TypePoint3F, Offset(damageEmitterOffset, RTSUnitData), VC_MAX_NUM_DAMAGE_EMITTERS); addField("damageLevelTolerance", TypeF32, Offset(damageLevelTolerance, RTSUnitData), VC_MAX_NUM_DAMAGE_EMITTERS); addField("numDamageEmitters", TypeF32, Offset(numDamageEmitters, RTSUnitData)); addField("ShowAllDamageEmmiters",TypeBool, Offset(bShowAllDamageEmmiters, RTSUnitData));in RTSUnit::RTSUnit() after
add
in onAdd() after
if (!Parent::onAdd()) return false;add
U32 j; for( j=0; j<RTSUnitData::VC_MAX_NUM_DAMAGE_EMITTERS; j++ ) { RTSUnitData *pDB = dynamic_cast<RTSUnitData*>(mDataBlock); if( pDB->damageEmitterList[j] ) { mDamageEmitterList[j] = new ParticleEmitter; mDamageEmitterList[j]->onNewDataBlock( pDB->damageEmitterList[j] ); if( !mDamageEmitterList[j]->registerObject() ) { Con::warnf( ConsoleLogEntry::General, "Could not register damage emitter for class: %s", pDB->getName() ); delete mDamageEmitterList[j]; mDamageEmitterList[j] = NULL; } } }in onRemove()
add at the top of the method
for( int i=0; i<RTSUnitData::VC_MAX_NUM_DAMAGE_EMITTERS; i++ ) { if( mDamageEmitterList[i] ) { mDamageEmitterList[i]->deleteWhenEmpty(); mDamageEmitterList[i] = NULL; } }in advanceTime(F32 dt) after
if (mImpactSound) playImpactSound();add
after the entire setDirty() method, add a new method
void RTSUnit::updateDamageSmoke( F32 dt ) { RTSUnitData *pDB = dynamic_cast<RTSUnitData*>(mDataBlock); F32 damagePercent = mDamage / pDB->maxDamage; if (pDB->numDamageEmitters >= RTSUnitData::VC_MAX_NUM_DAMAGE_EMITTERS) { pDB->numDamageEmitters = RTSUnitData::VC_MAX_NUM_DAMAGE_EMITTERS; } for( int i=pDB->numDamageEmitters-1; i>=0; i-- ) { if( damagePercent < pDB->damageLevelTolerance[i]) continue; Point3F offset = pDB->damageEmitterOffset[i]; MatrixF trans = getTransform(); trans.mulP( offset ); if( mDamageEmitterList[i] ) { Point3F emitterPoint = offset; mDamageEmitterList[i]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), (U32)(dt * 1000)); } if (!pDB->bShowAllDamageEmmiters) break; } }Now you can compile/link the RTS engine code