Random Explosions For Projectiles

Scripts and code that enhance the gameplay functionality of the engine.
9 posts Page 1 of 1
Steve_Yorkshire
Posts: 208
Joined: Tue Feb 03, 2015 10:30 pm
 
  by Steve_Yorkshire » Tue Apr 05, 2016 9:32 pm
Projectiles are a little boring having only one explosion and if you want to use an animated particle sprite sheet or an explosion model it's going to get even more repetitive.

Here's the general idea, the projectile has up to 4 explosion datablocks instead of 1. On exploding the projectile looks up how many explosion datablocks in can reference and then randomly chooses one. If it can't find any additional explosions it will default to the single stock one.

Obviously you can do the same with water explosions, but ehre I'm only using dirt/dry explosions.

I am currently in "art mode" so here's a -
**warning of terrible C++ ahead**
- feel free to expand on the idea and make it less horrible.

First up, add new datablocks and information for explosions to the projectile. Here I am using the stock projectile in art/datablocks/Lurker.cs. This requires adding new particles and emitters, as well as editing the stock BulletDirtExplosion emitters to make it easier to see.

//...
datablock ParticleData(BulletDirtDust)
{
textureName = "art/particles/impact";
dragCoefficient = "1";
gravityCoefficient = "-0.100122";
windCoefficient = 0;
inheritedVelFactor = 0.0;
constantAcceleration = "-0.83";
lifetimeMS = 800;
lifetimeVarianceMS = 300;
spinRandomMin = -180.0;
spinRandomMax = 180.0;
useInvAlpha = true;

colors[0] = "0.496063 0.393701 0.299213 0.692913";
colors[1] = "0.692913 0.614173 0.535433 0.346457";
colors[2] = "0.897638 0.84252 0.795276 0";

sizes[0] = "0.997986";
sizes[1] = "2";
sizes[2] = "2.5";

times[0] = 0.0;
times[1] = "0.498039";
times[2] = 1.0;
animTexName = "art/particles/impact";
};

datablock ParticleEmitterData(BulletDirtDustEmitter)
{
ejectionPeriodMS = 20;
periodVarianceMS = 10;
ejectionVelocity = "1";
velocityVariance = 1.0;
thetaMin = 0.0;
thetaMax = 180.0;
lifetimeMS = 250;
particles = "BulletDirtDust";
blendStyle = "ADDITIVE";//yorks "NORMAL";
};

//yorks start
datablock ParticleData(BulletDirtDust1)
{
textureName = "art/particles/impact";
dragCoefficient = "1";
gravityCoefficient = "-0.1";
windCoefficient = 0;
inheritedVelFactor = 0.0;
constantAcceleration = "-0.83";
lifetimeMS = 800;
lifetimeVarianceMS = 300;
spinRandomMin = -180.0;
spinRandomMax = 180.0;
useInvAlpha = true;

colors[0] = "1 0 0 0";
colors[1] = "1 0 0.0 0.5";
colors[2] = "1 0 0.0 0";

sizes[0] = "1";
sizes[1] = "2";
sizes[2] = "2.5";

times[0] = 0.0;
times[1] = "0.5";
times[2] = 1.0;
animTexName = "art/particles/impact";
};

datablock ParticleEmitterData(BulletDirtDust1Emitter)
{
ejectionPeriodMS = 20;
periodVarianceMS = 10;
ejectionVelocity = "1";
velocityVariance = 1.0;
thetaMin = 0.0;
thetaMax = 180.0;
lifetimeMS = 250;
particles = "BulletDirtDust1";
blendStyle = "ADDITIVE";
};

datablock ParticleData(BulletDirtDust2)
{
textureName = "art/particles/impact";
dragCoefficient = "1";
gravityCoefficient = "-0.1";
windCoefficient = 0;
inheritedVelFactor = 0.0;
constantAcceleration = "-0.83";
lifetimeMS = 800;
lifetimeVarianceMS = 300;
spinRandomMin = -180.0;
spinRandomMax = 180.0;
useInvAlpha = true;

colors[0] = "0.0 0.0 1 1";
colors[1] = "0.0 0.0 1 0.5";
colors[2] = "0.0 0.0 1 0";

sizes[0] = "1";
sizes[1] = "2";
sizes[2] = "3";

times[0] = 0.0;
times[1] = "0.5";
times[2] = 1.0;
animTexName = "art/particles/impact";
};

datablock ParticleEmitterData(BulletDirtDust2Emitter)
{
ejectionPeriodMS = 20;
periodVarianceMS = 10;
ejectionVelocity = "1";
velocityVariance = 1.0;
thetaMin = 0.0;
thetaMax = 180.0;
lifetimeMS = 250;
particles = "BulletDirtDust2";
blendStyle = "ADDITIVE";
};

datablock ParticleData(BulletDirtDust3)
{
textureName = "art/particles/impact";
dragCoefficient = "1";
gravityCoefficient = "-0.1";
windCoefficient = 0;
inheritedVelFactor = 0.0;
constantAcceleration = "-0.83";
lifetimeMS = 800;
lifetimeVarianceMS = 300;
spinRandomMin = -180.0;
spinRandomMax = 180.0;
useInvAlpha = true;

colors[0] = "0.0 1 0 1";
colors[1] = "0.0 1 0 0.5";
colors[2] = "0.0 1 0 0";

sizes[0] = "1";
sizes[1] = "2";
sizes[2] = "3";

times[0] = 0.0;
times[1] = "0.5";
times[2] = 1.0;
animTexName = "art/particles/impact";
};

datablock ParticleEmitterData(BulletDirtDust3Emitter)
{
ejectionPeriodMS = 20;
periodVarianceMS = 10;
ejectionVelocity = "1";
velocityVariance = 1.0;
thetaMin = 0.0;
thetaMax = 180.0;
lifetimeMS = 250;
particles = "BulletDirtDust3";
blendStyle = "ADDITIVE";
};
//yorks end

//-----------------------------------------------------------------------------
// Explosion
//-----------------------------------------------------------------------------
datablock ExplosionData(BulletDirtExplosion)
{
soundProfile = BulletImpactSound;
lifeTimeMS = 65;

// Volume particles
particleEmitter = BulletDirtDustEmitter;
particleDensity = 4;
particleRadius = 0.3;

//yorks start
/*
// Point emission
emitter[0] = BulletDirtSprayEmitter;
emitter[1] = BulletDirtSprayEmitter;
emitter[2] = BulletDirtRocksEmitter;
*/
// Point emission
emitter[0] = BulletDirtDustEmitter;
emitter[1] = BulletDirtDustEmitter;
emitter[2] = BulletDirtDustEmitter;
//yorks end
};

//yorks start
datablock ExplosionData(BulletDirtExplosion1)
{
soundProfile = BulletImpactSound;
lifeTimeMS = 65;

// Volume particles
particleEmitter = BulletDirtDust1Emitter;
particleDensity = 4;
particleRadius = 0.3;

// Point emission
emitter[0] = BulletDirtDust1Emitter;
emitter[1] = BulletDirtDust1Emitter;
emitter[2] = BulletDirtDust1Emitter;
};

datablock ExplosionData(BulletDirtExplosion2)
{
soundProfile = BulletImpactSound;
lifeTimeMS = 65;

// Volume particles
particleEmitter = BulletDirtDust2Emitter;
particleDensity = 4;
particleRadius = 0.3;

// Point emission
emitter[0] = BulletDirtDust2Emitter;
emitter[1] = BulletDirtDust2Emitter;
emitter[2] = BulletDirtDust2Emitter;
};

datablock ExplosionData(BulletDirtExplosion3)
{
soundProfile = BulletImpactSound;
lifeTimeMS = 65;

// Volume particles
particleEmitter = BulletDirtDust3Emitter;
particleDensity = 4;
particleRadius = 0.3;

// Point emission
emitter[0] = BulletDirtDust3Emitter;
emitter[1] = BulletDirtDust3Emitter;
emitter[2] = BulletDirtDust3Emitter;
};
//yorks end

//...

datablock ProjectileData( BulletProjectile )
{
projectileShapeName = "";

directDamage = 5;
radiusDamage = 0;
damageRadius = 0.5;
areaImpulse = 0.5;
impactForce = 1;

explosion = BulletDirtExplosion;
decal = BulletHoleDecal;

//yorks start
explosion1 = BulletDirtExplosion1;
explosion2 = BulletDirtExplosion2;
explosion3 = BulletDirtExplosion3;
//yorks end

muzzleVelocity = 120;
velInheritFactor = 1;

armingDelay = 0;
lifetime = 992;
fadeDelay = 1472;
bounceElasticity = 0;
bounceFriction = 0;
isBallistic = false;
gravityMod = 1;
};
//...


So we have now have 4 explosions which will show white (original), red, blue and green so we can see the difference.

Next up we need to crack open the C++. Open up soure/T3D/projectile.h and list the new explosion data.

/...
ExplosionData* explosion;
S32 explosionId;

//yorks start
ExplosionData* explosion1;
S32 explosion1Id;

ExplosionData* explosion2;
S32 explosion2Id;

ExplosionData* explosion3;
S32 explosion3Id;
//yorks end

ExplosionData* waterExplosion; // Water Explosion Datablock
//...


And then the meat of the sandwich, source/T3D/projectile.cpp.

ProjectileData::ProjectileData()
{
//...
explosion = NULL;
explosionId = 0;

//yorks start
explosion1 = NULL;
explosion1Id = 0;

explosion2 = NULL;
explosion2Id = 0;

explosion3 = NULL;
explosion3Id = 0;
//yorks end

waterExplosion = NULL;
//...
}

//...

void ProjectileData::initPersistFields()
{
//...
addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, ProjectileData),
"@brief Explosion datablock used when the projectile explodes outside of water.\n\n");
addField("waterExplosion", TYPEID< ExplosionData >(), Offset(waterExplosion, ProjectileData),
"@brief Explosion datablock used when the projectile explodes underwater.\n\n");

//yorks start
addField("explosion1", TYPEID< ExplosionData >(), Offset(explosion1, ProjectileData),
"@brief Explosion1 datablock used when the projectile explodes outside of water.\n\n");
addField("explosion2", TYPEID< ExplosionData >(), Offset(explosion2, ProjectileData),
"@brief Explosion2 datablock used when the projectile explodes outside of water.\n\n");
addField("explosion3", TYPEID< ExplosionData >(), Offset(explosion3, ProjectileData),
"@brief Explosion3 datablock used when the projectile explodes outside of water.\n\n");
//yorks end

addField("splash", TYPEID< SplashData >(), Offset(splash, ProjectileData),
"@brief Splash datablock used to create splash effects as the projectile enters or leaves water\n\n");
//...
}

//...

bool ProjectileData::preload(bool server, String &errorStr)
{
//...
if (!explosion && explosionId != 0)
if (Sim::findObject(explosionId, explosion) == false)
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion): %d", explosionId);

//yorks start
if (!explosion1 && explosion1Id != 0)
if (Sim::findObject(explosion1Id, explosion1) == false)
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion1): %d", explosion1Id);

if (!explosion2 && explosion2Id != 0)
if (Sim::findObject(explosion2Id, explosion2) == false)
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion2): %d", explosion2Id);

if (!explosion3 && explosion3Id != 0)
if (Sim::findObject(explosion3Id, explosion3) == false)
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion3): %d", explosion3Id);
//yorks end

if (!waterExplosion && waterExplosionId != 0)
//...
}

//--------------------------------------------------------------------------
void ProjectileData::packData(BitStream* stream)
{
//...
if (stream->writeFlag(explosion != NULL))
stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);

//yorks start
if (stream->writeFlag(explosion1 != NULL))
stream->writeRangedU32(explosion1->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);

if (stream->writeFlag(explosion2 != NULL))
stream->writeRangedU32(explosion2->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);

if (stream->writeFlag(explosion3 != NULL))
stream->writeRangedU32(explosion3->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
//yorks end

if (stream->writeFlag(waterExplosion != NULL))
//...
}

void ProjectileData::unpackData(BitStream* stream)
{
//...
if (stream->readFlag())
explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);

//yorks start
if (stream->readFlag())
explosion1Id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);

if (stream->readFlag())
explosion2Id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);

if (stream->readFlag())
explosion3Id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
//yorks end

if (stream->readFlag())
//...
}

//...

void Projectile::explode( const Point3F &p, const Point3F &n, const U32 collideType )
{
//...
else
{
// Client just plays the explosion at the right place...
//
Explosion* pExplosion = NULL;

//yorks in
S32 mRandomize = 0;

if (mDataBlock->waterExplosion && pointInWater(p))
{
pExplosion = new Explosion;
pExplosion->onNewDataBlock(mDataBlock->waterExplosion, false);
}
else
/*yorks out start
if (mDataBlock->explosion)
{
pExplosion = new Explosion;
pExplosion->onNewDataBlock(mDataBlock->explosion, false);
}
//yorks out end */
//yorks in start
{
//yorks start with 1 continue check with others if exists else default to normal explosion
if (mDataBlock->explosion1)
{
//yorks we have >1 explosion, check for others
if (mDataBlock->explosion2)
{
if (mDataBlock->explosion3)
mRandomize = 3;
else
mRandomize = 2;
}
else
mRandomize = 1;
}

//yorks get the random
if (mRandomize == 0)
{
//stock explosion
if (mDataBlock->explosion)
{
pExplosion = new Explosion;
pExplosion->onNewDataBlock(mDataBlock->explosion, false);
}
}
else
{
//yorks get random number betweem 0 and mRandomize
S32 mChoose = (gRandGen.randI(0, mRandomize));

if (mChoose == 1)
{
pExplosion = new Explosion;
pExplosion->onNewDataBlock(mDataBlock->explosion1, false);
}
else
{
if (mChoose == 2)
{
pExplosion = new Explosion;
pExplosion->onNewDataBlock(mDataBlock->explosion2, false);
}
else
{
if (mChoose == 3)
{
pExplosion = new Explosion;
pExplosion->onNewDataBlock(mDataBlock->explosion3, false);
}
else
{
pExplosion = new Explosion;
pExplosion->onNewDataBlock(mDataBlock->explosion, false);
}
}
}
}
}
//yorks in end

if( pExplosion )
//...
}

//...


And that should be that. It ain't elegant but it works, as I said up top, feel free to improve it (no really plz). :lol:

And this is what it looks like in action:

TRON
Posts: 32
Joined: Tue Feb 03, 2015 8:55 pm
by TRON » Tue Apr 05, 2016 9:35 pm
Looks at C++ code
OMG, my Eyes! My eyes!!
Steve_Yorkshire
Posts: 208
Joined: Tue Feb 03, 2015 10:30 pm
 
by Steve_Yorkshire » Tue Apr 05, 2016 9:45 pm
Duion
Posts: 844
Joined: Sun Feb 08, 2015 1:51 am
 
by Duion » Wed Apr 06, 2016 12:03 am
Whats wrong with the C++ ?
deathbravo
Posts: 50
Joined: Mon Feb 01, 2016 7:06 am
by deathbravo » Wed Apr 06, 2016 4:05 am
nothing wrong.
artistic mode
rlranft
Posts: 298
Joined: Thu Feb 05, 2015 3:11 pm
 
by rlranft » Wed Apr 06, 2016 7:31 am
I dunno, I kind of like giant if/else blocks....
Duion
Posts: 844
Joined: Sun Feb 08, 2015 1:51 am
 
by Duion » Wed Apr 06, 2016 11:19 am
I have seen much bigger if/else blocks from steve.
Azaezel
Posts: 385
Joined: Tue Feb 03, 2015 9:50 pm
 
by Azaezel » Fri Apr 08, 2016 5:07 am
Nice idea. as a note on the

Code: Select all

   ExplosionData* explosion;
   S32 explosionId;

variants:
https://github.com/GarageGames/Torque3D ... e.cpp#L203 that PDC_NUM_KEYS bit's how you can pack those into a script-exposed array.
Steve_Yorkshire
Posts: 208
Joined: Tue Feb 03, 2015 10:30 pm
 
by Steve_Yorkshire » Wed May 04, 2016 12:06 am
I've had a bit more of play around using the subExplosion type found in explosion.cpp/h:

//projectile.h
class ProjectileData : public GameBaseData
{
//...
public:

enum ExplosionConsts
{
EC_MAX_EXPLOSIONS = 4,
};

// variables set in datablock definition:

//...

S32 fadeDelay; // the IRangeValidatorScaled field validator

/*
ExplosionData* explosion;
S32 explosionId;
*/

ExplosionData* explosionList[EC_MAX_EXPLOSIONS];
S32 explosionIDList[EC_MAX_EXPLOSIONS];

ExplosionData* waterExplosion; // Water Explosion Datablock
//...


//projectile.cpp 
//...
void ProjectileData::initPersistFields()
{
//...
//addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, ProjectileData),
// "@brief Explosion datablock used when the projectile explodes outside of water.\n\n");

addField("explosion", TYPEID< ExplosionData >(), Offset(explosionList, ProjectileData), EC_MAX_EXPLOSIONS,
"List of additional ExplosionData objects to create at the start of the "
"explosion.");

addField("waterExplosion", TYPEID< ExplosionData >(), Offset(waterExplosion, ProjectileData),
"@brief Explosion datablock used when the projectile explodes underwater.\n\n");
//...
}

//...
bool ProjectileData::preload(bool server, String &errorStr)
{
//...
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(particleWaterEmitter): %d", particleWaterEmitterId);

/*
if (!explosion && explosionId != 0)
if (Sim::findObject(explosionId, explosion) == false)
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion): %d", explosionId);
*/

for (S32 k = 0; k<EC_MAX_EXPLOSIONS; k++)
{
if (!explosionList[k] && explosionIDList[k] != 0)
{
if (Sim::findObject(explosionIDList[k], explosionList[k]) == false)
{
Con::errorf(ConsoleLogEntry::General, "ExplosionData::onAdd: Invalid packet, bad datablockId(explosion): 0x%x", explosionIDList[k]);
}
}
}

if (!waterExplosion && waterExplosionId != 0)
//...
}
//...

void ProjectileData::packData(BitStream* stream)
{
//...
/*
if (stream->writeFlag(explosion != NULL))
stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
*/
for (S32 i = 0; i<EC_MAX_EXPLOSIONS; i++)
{
if (stream->writeFlag(explosionList != NULL))
{
stream->writeRangedU32(explosionList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
}
}

if (stream->writeFlag(waterExplosion != NULL))
//...
}

void ProjectileData::unpackData(BitStream* stream)
{
//...
/*
if (stream->readFlag())
explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
*/

for (S32 k = 0; k<EC_MAX_EXPLOSIONS; k++)
{
if (stream->readFlag())
{
explosionIDList[k] = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
}
}

if (stream->readFlag())
waterExplosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
//...
}
//...

void Projectile::explode( const Point3F &p, const Point3F &n, const U32 collideType )
{
//...
}


However it all rather ground to a halt because I am not familiar with how to get the explosionList[EC_MAX_EXPLOSIONS] in the actual [i]explode()
function of projectile.cpp. :?
9 posts Page 1 of 1

Who is online

Users browsing this forum: No registered users and 1 guest