Random Explosions For Projectiles

Scripts and code that enhance the gameplay functionality of the engine.
9 posts Page 1 of 1
Steve_Yorkshire
Posts: 252
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: 34
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: 252
Joined: Tue Feb 03, 2015 10:30 pm
 
by Steve_Yorkshire » Tue Apr 05, 2016 9:45 pm
@ TRON
Image

:P
Duion
Posts: 997
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: 997
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: 395
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: 252
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->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 explode() function of projectile.cpp. :?
9 posts Page 1 of 1

Who is online

Users browsing this forum: No registered users and 1 guest