Game Development Community

Advanced Weapon Recoil System - Force Feadback System (Full Physical)

by Matt "Mr. Pig" Razza · 07/03/2006 (11:36 am) · 43 comments

This resource was created because many people have attempted to create a recoil system (I posted mine on the forums - but it was script and they got mad - so I ported it to the engine) and failed.

Please include Valde Games or Matt Razza in your Special Thanks page. :)

What this system does:
Each time a mounted object (a shapeBaseImage mounted to a shapeBase) enters the fire state (the state with stateFire set to true) the engine will move the camera according to settings set in the shapeBaseImage's datablock. There is also a bit of random fluctuation you can set and the recoil increases the longer you hold down the fire key (then decreases when you let go - forcing (or encouraging) the user to fire in bursts).

Let's begin, you will be editing the following files today:
shapeBase.cc
shapeImage.cc
shapeBase.h


You'll need to add the following includes at the bottom of the stack in shapeImage.cc:
#include "math/mRandom.h"

First we are going to add the required variables to the datablocks header file. In "shapeBase.h", around line 365 (and after "virtual void unpackData(BitStream* stream);") add the following:
/// Recoil related meathods
   /// Code (C) Valde Games
   F32 recoilPitchFactor;
   F32 recoilPitchRandomize;
   F32 recoilYawFactor;
   F32 recoilYawRandomize;
   F32 shotFactor; //Client only so server and client will be out of sync

   F32 calcRecoil(F32 recoilFactor, F32 recoilRandomize);
   F32 getShotFactor();

Now let's add the vars to the constructor. In "shapeImage.cc", around line 162, add:
recoilPitchFactor = 0;
   recoilPitchRandomize = 0;
   recoilYawFactor = 0;
   recoilYawRandomize = 0;
   shotFactor = 1;

Now that the vars will be set to the default setting we need to expose them to the datablock in script. To do so keep open "shapeImage.cc" and add the following around line 432:
//Recoil
   addNamedField(recoilPitchFactor, TypeF32, ShapeBaseImageData);
   addNamedField(recoilPitchRandomize, TypeF32, ShapeBaseImageData);
   addNamedField(recoilYawFactor, TypeF32, ShapeBaseImageData);
   addNamedField(recoilYawRandomize, TypeF32, ShapeBaseImageData);

Now we are going to add the functions that will calculate the amount of recoil to be applied based on the values set in the datablock. In "shapeImage.cc", around line 1485, add the following:
//===========================================================================
//Name: calcRecoil
//Description: Calculates the recoil to be applied
//Code by Valde Games (Matt Razza)
//---------------------------------------------------------------------------
F32 ShapeBaseImageData::calcRecoil(F32 recoilFactor, F32 recoilRandomize)
{
	MRandomLCG randomNumbers;
	F32 recoil; //Return var
	F32 factor = randomNumbers.randI(-1 * recoilRandomize, recoilRandomize); //Get random factor

	//Calc factor (if negitive)
	if (factor < 0)
	{
		factor = (-1 * factor) / recoilRandomize; //Fix recoil
	}
	else if (factor == 0)
	{
		factor = recoilRandomize; //Have some recoil
	}

	recoil = recoilFactor * factor; //Get recoil

	recoil = recoil * getShotFactor();

	return recoil;
}

//===========================================================================
//Name: getShotFactor
//Description: Returns the shot factor
//Code by Valde Games (Matt Razza)
//---------------------------------------------------------------------------
F32 ShapeBaseImageData::getShotFactor()
{
	return shotFactor;
}

Now that all the functions are added let's add the code that will trigger the recoil to be applied. Still in "shapeImage.cc", around line 1531, in the "void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)" function. Add:
//If client is firing, do recoil
   if (!isServerObject() && newState == image.dataBlock->fireState && getControllingClientID() == dAtoi(Con::getVariable("$Client::gameConnection"))))
   {
	   //Gather info
	   MRandomLCG randomNumbers;
	   F32 currentPitch = Con::getFloatVariable("$mvPitch");
	   F32 currentYaw = Con::getFloatVariable("$mvYaw");
	   F32 recoilPitch = image.dataBlock->calcRecoil(image.dataBlock->recoilPitchFactor, image.dataBlock->recoilPitchRandomize);
	   F32 recoilYaw = image.dataBlock->calcRecoil(image.dataBlock->recoilYawFactor, image.dataBlock->recoilYawRandomize);

	   //Do recoil
	   Con::setFloatVariable("$mvPitch", currentPitch - recoilPitch);

	   if (randomNumbers.randI(1, 4) == 3) //Left recoil
	   {
		   Con::setFloatVariable("$mvYaw", currentYaw - recoilYaw);
	   }
	   else //Right recoil
	   {
		   Con::setFloatVariable("$mvYaw", currentYaw + recoilYaw);
	   }

	   //Add to shot factor
	   if (image.dataBlock->getShotFactor() < 1.2)
	   {
			image.dataBlock->shotFactor += 0.05;
	   }
   }

After:
if (!mMountedImageList[imageSlot].dataBlock)
      return;
   MountedImage& image = mMountedImageList[imageSlot];

Now the recoil will be applied each time the shapeBaseImage enters the onFire state. Also the recoil factor will increase after each shot. Now we need to have it decrease back to 1 over time. To do so, open "shapeBase.cc" and around line 969, after the "if (isServerObject())" code add:
else
   {
	   for (int x = 0; x < MaxMountedImages; x++) //Check each image
	   {
		   if (mMountedImageList[x].dataBlock != NULL) //Check if one is mounted
		   {
			   if (mMountedImageList[x].dataBlock->shotFactor > 1) //Check if we need to change it
			   {
					mMountedImageList[x].dataBlock->shotFactor -= 0.05; //Change shot factor
			   }
		   }
	   }
   }

Let's just add network support. At the bottom of the packData function in "shapeImage.cc", around line 545 add:
//Recoil data
	stream->write(recoilPitchFactor);
	stream->write(recoilPitchRandomize);
	stream->write(recoilYawFactor);
	stream->write(recoilYawRandomize);

Then at the bottom of the unpackData function, around line 669 add:
//Recoil data
	stream->read(&recoilPitchFactor);
	stream->read(&recoilPitchRandomize);
	stream->read(&recoilYawFactor);
	stream->read(&recoilYawRandomize);

Once network support for the shapeBase area has been added, we need to add network support for the rest. We will be adding clientID tracking the gameBase. Now ALL gameBase objects will have full support for client ID's. Please note that only the ID is being replicated, not the full gameConnection.

To do so open "gameBase.h" and add the following in the gameBase class under "private:" (around line 175:
S32 mControllingClientID;



Then add the following under "public:" (around line 341):
S32 getControllingClientID() { return mControllingClientID; }



Now open "gameBase.cc" and add the following to the "packUpdate" function, around line 459:
if (stream->writeFlag((mask & ControlMask) && mControllingClient != 0))
   {
		stream->write(dAtoi(mControllingClient->getIdString()));
   }



Lastly we need to add the required "unpackUpdate" code, add the following around line 484:
if (stream->readFlag())
   {
		stream->read(&mControllingClientID);
   }

Now we are done with the engine. Open each of your weapon's datablocks (the "datablock ShapeBaseImageData()" thing) and add the following:
//Weapon Recoil Hooks
	recoilPitchFactor = 0.025; //Pitch recoil
	recoilPitchRandomize = 2; //Range to randomize (-/+)
	recoilYawFactor = 0.0125; //Yaw recoil
	recoilYawRandomize = 2; //Range to randomize (-/+)

Finally we need to track what the server thinks our client ID is to do full perdictions. In script (any file) add the following function:
function clientCmdsetID(%id)
{
	$Client::gameConnection = %id;
}



Then add the following line in the function "gameConnection::onConnect" in clientConnection.cs which should be located in the common directory:
commandToClient(%client, 'setID', %client); //Tell the client it's ID

Feel free to change the values - that's what I use from my AK47.

That's it! I may have left something stupid out as I coded this yesterday and don't remember everything I changed. If it doesn't work, let me know and I'll look for what I missed.
Page «Previous 1 2 3 Last »
#1
04/12/2006 (8:47 am)
Oh - just a note. It works on-line.
#2
04/12/2006 (12:56 pm)
OWNAGE. Implementing ASAP.
#3
04/12/2006 (1:07 pm)
Have you thought about doing this as CameraShake effect?

Same as with the "falling/landing shake". Shouldn't be hard either and the engine has native support for it.

-- Markus
#4
04/12/2006 (2:15 pm)
Yeah I just noticed that this method really is not what I'm looking for; the camera shake method would be much better, rather than the yaw/pitch modifications, which I find to be a bad implementation of recoil. The camera shake of Tim Heldna works great, though lacking networkability.

What I'd like to see in a recoil system, is Tim Heldna's camera shake that increases in its amount depending on how long you fire. It would be perfect. Fire one or two shots, low camera shake. Fire a 30 round magazine consistently, have the camera shake increase in amount up to a set limit.

Maybe just modify this, instead of yaw/pitch, shake the camera and its values.

I'd pay for that to be done. lol
#5
04/12/2006 (5:24 pm)
I'll do that now if your interested. I am using Yaw/Pitch as it is giving the effect I am looking for (basing our system off of another less popular game Frag.Ops).

Give me a moment and I'll work on camera shake.
#6
04/12/2006 (5:35 pm)
Hey, thanks. :)
#7
04/12/2006 (6:13 pm)
My resource worked? I didn't know if I left out "You'll need to add this include to the top of shapeImage.cc:

#include "game/fx/cameraFXMgr.h""
#8
04/12/2006 (6:31 pm)
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=10243

I didn't test it.

Just guessing it will work. Where's my $? :P
#9
04/12/2006 (6:37 pm)
oops I don't think it worked on a network before (left some code out) it should now..
#10
04/12/2006 (6:47 pm)
Yeah it doesn't say about the cameraFXMgr.h file. Also there's a slight typo:

addNamedField(recoil[b]Frequency[/b]Randomize, TypeF32, ShapeBaseImageData);

Forgot the first "e" in frequency.
#11
04/12/2006 (6:49 pm)
rofl. Nice. See, told you I didn't test it.
#12
04/27/2006 (2:04 pm)
Fixed online.
#13
06/07/2006 (5:37 pm)
I lied, online is still broken.

I will be updating the code with a fix shortly.
#14
06/08/2006 (3:49 pm)
Recoil fixed.
#15
07/03/2006 (1:28 pm)
I'd really like to see your script version of this. I am unable to compile my engine at this time.
#16
07/03/2006 (2:25 pm)
Thanks Mr. Pig! I'll be using this ASAP. =)
#17
07/05/2006 (11:04 pm)
It can be found here:
http://www.garagegames.com/mg/forums/result.thread.php?qt=39870
#18
07/06/2006 (2:03 am)
I seem to be having some trouble putting this:
/// Recoil related methods
   /// Code (C) Valde Games
   F32 recoilPitchFactor;
   F32 recoilPitchRandomize;
   F32 recoilYawFactor;
   F32 recoilYawRandomize;
   F32 shotFactor; //Client only so server and client will be out of sync

   F32 calcRecoil(F32 recoilFactor, F32 recoilRandomize);
   F32 getShotFactor();
code into the right place... The compile errors with an 'undeclared identifier' for each one of these, could you tell me what the first few lines above this code are? I thought I had it compiled at some point but I was apparently mistaken.

edit: I put it in shapeBase.h around line 365ish under the unpackData... line where the instructions said. It still won't compile.
#19
07/06/2006 (8:25 am)
Sure thing, I guess I just expected everyone to know what class to put the code in (also by the line number). One second I'll load up my code and give you more detail.

If it still won't compile let me know of the errors - I may have left something out...

----------------------------------------------------
I just put it at the bottom of the "ShapeBaseImageData" class header. It could be anywere in the class... before where I put it:

/// @name State Array
   ///
   /// State array is initialized onAdd from the individual state
   /// struct array elements.
   ///
   /// @{
   StateData state[MaxStates];   ///< Array of states.
   bool      statesLoaded;       ///< Are the states loaded yet?
   /// @}

   /// @name Infrastructure
   ///
   /// Miscellaneous inherited methods.
   /// @{

   DECLARE_CONOBJECT(ShapeBaseImageData);
   ShapeBaseImageData();
   ~ShapeBaseImageData();
   bool onAdd();
   bool preload(bool server, char errorBuffer[256]);
   S32 lookupState(const char* name);  ///< Get a state by name.
   static void initPersistFields();
   virtual void packData(BitStream* stream);
   virtual void unpackData(BitStream* stream);
   /// @}

   /// Recoil related meathods
   /// Code (C) Valde Games
   F32 recoilPitchFactor;
   F32 recoilPitchRandomize;
   F32 recoilYawFactor;
   F32 recoilYawRandomize;
   F32 shotFactor; //Client only so server and client will be out of sync

   F32 calcRecoil(F32 recoilFactor, F32 recoilRandomize);
   F32 getShotFactor();
#20
07/07/2006 (6:33 am)
Awesome resource. Works well :)
Page «Previous 1 2 3 Last »