Jump to content

FX- and Bezier projectiles


LukasPJ

Recommended Posts

Hey guys, this resource adds 2 new Projectile classes that is especially useful for effects.


The first is the "FXProjectile" which both adds the OnlyCollideWithTarget and the Homing features. They should be self explanatory. Furthermore this projectile class is more inheritance friendly and makes it easier to add new projectile classes such as the "BezierProjectile" which makes the projectile fly from A to B following a bezier curve.


The FXProjectile can be downloaded here.

The BezierProjectile can be downloaded here.


Example of use:

 

datablock BezierProjectileData(DefaultBezierProjectile)
{
   projectileShapeName = "";
 
   muzzleVelocity = 10;
   velInheritFactor = 0;
   armingDelay = 0;
   particleEmitter = DefaultBezierProjectileEmitter;
   lifetime = 2000;
   fadeDelay = 2000;
   isBallistic = false;
   bounceElasticity = 0;
   bounceFriction = 0;
   gravityMod = 0;
   hasLight = false;
   lightRadius = 3;
   lightColor = "0.8 0.8 1.0";
 
   bezierweights = "15 0 25";
};
 
function ThrowBezierProjectile(%start, %end, %data, %ignoreCol, %weights)
{
   %vel = VectorSub(%end, %start);
   %vel = VectorNormalize(%vel);
   %vel = VectorScale(%vel, %data.muzzleVelocity);
   %proj = new BezierProjectile() {
      initialPosition = %start;
      initialVelocity = %vel;
      homing = false;
      BezierWeights = %weights;
      targetPosition = %end;
      datablock = %data;
   };
}

 

And video of that function here (mute it):

muGGV2G9XpM


Lastly, this old video of mine shows a use case for the projectiles:

nveTKVZqBHo

Link to comment
Share on other sites

  • 8 months later...
Noticed a small "jitter" on collision when using FXProjectile with homing, targetObject and onlyCollideWithTarget. Doesn't seem to do anything else, just doesn't delete/vanish on impact. Did look at the code but couldn't see an obvious reason.

Remind me to look into this over the weekend :P

Link to comment
Share on other sites

Had another look at it with a clear head and realised why it's not working.


The "onlyCollideWithTarget" var is set to teleport the projectile to the target at the end of it's lifetime and does not actually collide with it ... which is ... er ... odd. Might have been useful for whatever you originally envisioned but ... odd ... for general projectile use. The "onlyCollideWithTarget" var doesn't feature anywhere else except as a negative with collision checking.

void FXProjectile::simulate( F32 dt )
{
	if ( isServerObject() && mCurrTick >= mDataBlock->lifetime )
	{
		if(mTargetObject && mOnlyCollideWithTarget && mHoming)
		{
			VectorF normal = -getVelocity();
			normal.normalize();
			onCollision( getPosition(), normal, mTargetObject );
			explode( getPosition(), normal, mTargetObject->getTypeMask() );
		}
		deleteObject();
		return;
	}

 

So I commented that out and put in a positive check for targetObject, OnlyCollideWithTarget and homing vars. Nothing fancy, just working as normies would expect projectile to. So here's what the new simulate function looks like.

void FXProjectile::simulate( F32 dt )
{
	/* //yorks out - why Lucas why!? :P
	if ( isServerObject() && mCurrTick >= mDataBlock->lifetime )
	{
		if(mTargetObject && mOnlyCollideWithTarget && mHoming)
		{
			VectorF normal = -getVelocity();
			normal.normalize();
			onCollision( getPosition(), normal, mTargetObject );
			explode( getPosition(), normal, mTargetObject->getTypeMask() );
		}
		deleteObject();
		return;
	}
	*/
 
	if ( mHasExploded )
		return;
 
	// ... otherwise, we have to do some simulation work.
	RayInfo rInfo;
	Point3F oldPosition;
	Point3F newPosition;
 
	updatePosition(dt, oldPosition, newPosition);
 
	if(!(mTargetObject && mOnlyCollideWithTarget && mHoming))
	{
		// disable the source objects collision reponse for a short time while we
		// determine if the projectile is capable of moving from the old position
		// to the new position, otherwise we'll hit ourself
		bool disableSourceObjCollision = (mSourceObject.isValid() && mCurrTick <= SourceIdTimeoutTicks);
 
		if ( disableSourceObjCollision )
			mSourceObject->disableCollision();
 
		disableCollision();
 
		// Determine if the projectile is going to hit any object between the previous
		// position and the new position. This code is executed both on the server
		// and on the client (for prediction purposes). It is possible that the server
		// will have registered a collision while the client prediction has not. If this
		// happens the client will be corrected in the next packet update.
		// Raycast the abstract PhysicsWorld if a PhysicsPlugin exists.
		bool hit = false;
 
		if ( mPhysicsWorld )
			hit = mPhysicsWorld->castRay( oldPosition, newPosition, &rInfo, Point3F( newPosition - oldPosition) * mDataBlock->impactForce );
		else
			hit = getContainer()->castRay(oldPosition, newPosition, csmDynamicCollisionMask | csmStaticCollisionMask, &rInfo);
 
		if ( hit )
		{
			// make sure the client knows to bounce
			if ( isServerObject() && ( rInfo.object->getTypeMask() & csmStaticCollisionMask ) == 0 )
				setMaskBits( BounceMask );
 
			// Next order of business: do we explode on this hit?
			if ( mCurrTick > mDataBlock->armingDelay || mDataBlock->armingDelay == 0 )
			{
				if(!(mTargetObject && mOnlyCollideWithTarget && rInfo.object != mTargetObject) || !mOnlyCollideWithTarget)
				{
					MatrixF xform( true );
					xform.setColumn( 3, rInfo.point );
					setTransform( xform );
					mCurrPosition = rInfo.point;
					mCurrVelocity = Point3F::Zero;
 
					// Get the object type before the onCollision call, in case
					// the object is destroyed.
					U32 objectType = rInfo.object->getTypeMask();
 
					// re-enable the collision response on the source object since
					// we need to process the onCollision and explode calls
					if ( disableSourceObjCollision )
						mSourceObject->enableCollision();
 
					// Ok, here is how this works:
					// onCollision is called to notify the server scripts that a collision has occurred, then
					// a call to explode is made to start the explosion process. The call to explode is made
					// twice, once on the server and once on the client.
					// The server process is responsible for two things:
					// 1) setting the ExplosionMask network bit to guarantee that the client calls explode
					// 2) initiate the explosion process on the server scripts
					// The client process is responsible for only one thing:
					// 1) drawing the appropriate explosion
 
					// It is possible that during the processTick the server may have decided that a hit
					// has occurred while the client prediction has decided that a hit has not occurred.
					// In this particular scenario the client will have failed to call onCollision and
					// explode during the processTick. However, the explode function will be called
					// during the next packet update, due to the ExplosionMask network bit being set.
					// onCollision will remain uncalled on the client however, therefore no client
					// specific code should be placed inside the function!
					onCollision( rInfo.point, rInfo.normal, rInfo.object );
					explode( rInfo.point, rInfo.normal, objectType );
					// break out of the collision check, since we've exploded
					// we don't want to mess with the position and velocity
				}
			}
			else
			{
				if ( mDataBlock->isBallistic )
				{
					// Otherwise, this represents a bounce. First, reflect our velocity
					// around the normal...
					Point3F bounceVel = mCurrVelocity - rInfo.normal * (mDot( mCurrVelocity, rInfo.normal ) * 2.0);
					mCurrVelocity = bounceVel;
 
					// Add in surface friction...
					Point3F tangent = bounceVel - rInfo.normal * mDot(bounceVel, rInfo.normal);
					mCurrVelocity -= tangent * mDataBlock->bounceFriction;
 
					// Now, take elasticity into account for modulating the speed of the grenade
					mCurrVelocity *= mDataBlock->bounceElasticity;
 
					// Set the new position to the impact and the bounce
					// will apply on the next frame.
					//F32 timeLeft = 1.0f - rInfo.t;
					newPosition = oldPosition = rInfo.point + rInfo.normal * 0.05f;
				}
			}
		}
 
		// re-enable the collision response on the source object now
		// that we are done processing the ballistic movement
		if ( disableSourceObjCollision )
		mSourceObject->enableCollision();
		enableCollision();
	}
	else//yorks new down here for testing onlyCollideWithTarget when homing and there is a targetObject
	{
		bool disableSourceObjCollision = (mSourceObject.isValid() && mCurrTick <= SourceIdTimeoutTicks);
 
		if (disableSourceObjCollision)
			mSourceObject->disableCollision();
 
		disableCollision();
 
		bool hit = false;
 
		if (mPhysicsWorld)
			hit = mPhysicsWorld->castRay(oldPosition, newPosition, &rInfo, Point3F(newPosition - oldPosition) * mDataBlock->impactForce);
		else
			hit = getContainer()->castRay(oldPosition, newPosition, csmDynamicCollisionMask | csmStaticCollisionMask, &rInfo);
 
		if (hit)
		{
			// make sure the client knows to bounce
			if (isServerObject() && (rInfo.object->getTypeMask() & csmStaticCollisionMask) == 0)
				setMaskBits(BounceMask);
 
			// Next order of business: do we explode on this hit?
			if (mCurrTick > mDataBlock->armingDelay || mDataBlock->armingDelay == 0)
			{
				if (mTargetObject && mOnlyCollideWithTarget && rInfo.object == mTargetObject)
				{
					MatrixF xform(true);
					xform.setColumn(3, rInfo.point);
					setTransform(xform);
					mCurrPosition = rInfo.point;
					mCurrVelocity = Point3F::Zero;
 
					// Get the object type before the onCollision call, in case
					// the object is destroyed.
					U32 objectType = rInfo.object->getTypeMask();
 
					// re-enable the collision response on the source object since
					// we need to process the onCollision and explode calls
					if (disableSourceObjCollision)
						mSourceObject->enableCollision();
 
					onCollision(rInfo.point, rInfo.normal, rInfo.object);
					explode(rInfo.point, rInfo.normal, objectType);
				}
			}
			else
			{
				if (mDataBlock->isBallistic)
				{
					Point3F bounceVel = mCurrVelocity - rInfo.normal * (mDot(mCurrVelocity, rInfo.normal) * 2.0);
					mCurrVelocity = bounceVel;
 
					Point3F tangent = bounceVel - rInfo.normal * mDot(bounceVel, rInfo.normal);
					mCurrVelocity -= tangent * mDataBlock->bounceFriction;
 
					mCurrVelocity *= mDataBlock->bounceElasticity;
 
					newPosition = oldPosition = rInfo.point + rInfo.normal * 0.05f;
				}
			}
		}
 
		if (disableSourceObjCollision)
			mSourceObject->enableCollision();
		enableCollision();
	}//end of yorks
 
	if ( isClientObject() )
	{
		emitParticles( mCurrPosition, newPosition, mCurrVelocity, U32( dt * 1000.0f ) );
		updateSound();
	}
 
	mCurrDeltaBase = newPosition;
	mCurrBackDelta = mCurrPosition - newPosition;
	mCurrPosition = newPosition;
 
	MatrixF xform( true );
	xform.setColumn( 3, mCurrPosition );
	setTransform( xform );
}

 

And here's a video of some of it in action! :D

tog-HlPvBlM

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...