Archive for January, 2013

Weapon firing logic basically goes like this

    1. When the fire button is pressed, PlayerController.StartFire is called. The call is then pushed to Pawn.StartFire then Weapon.StartFire, passing a flag to indicate if it was a primary or secondary fire (or even tertiary if you want). The flag is just an integer and by default is either 0 or 1 for primary or secondary respectively.
    2. The call ends up in InventoryManager.SetPendingFire. The inventory manager has a PendingFire array, with each item in the array corresponding to a supported fire type i.e. for a player that has primary and secondary fire modes, the PendingFire array should have 2 elements. The value for each firing mode is 0 when the mode is not active and 1 when it is. InventoryManager.SetPendingFire sets 1 to whatever firing mode was requested.
      The default inventory manager does not initialise this array, which prevents a custom weapon from firing. To get this to work, we need to subclass the inventory manager and initialize the PendingFire array with the number of firing modes we have, lets set up 2 firing modes.

      class SKInventoryManager extends InventoryManager;
      
      defaultproperties
      {
          PendingFire(0)=0
          PendingFire(1)=0
      }

      We then need to tell our pawn to use this as its inventory manager.

      class SKPawn extends UDKPawn;
      ...
      defaultproperties
      {
      ...
      InventoryManagerClass=class'SKInventoryManager '
      ...
      }
      ...
    3. Next the weapon is sent from the Active state to a weapon firing state. Each firing mode needs to define what its weapon firing state is. e.g. for a weapon that can fire bullets on primary fire and grenades on secondary fire, you could define two states in your weapon class, WeaponFiringBullets and WeaponFiringGrenade. Then whenever primary fire is used, the first state would be activated and when secondary fire is used, the weapon would be sent to the second state. It is however not nessecary to have multiple states for each fire mode. If the logic is the same, as it often is, you could/should reuse the same state. Lets define the state for our fire modes within our weapon. We’ll use the already defined state – WeaponFiring
      class SKWeapon extends UDKWeapon;
      ...
      defaultproperties
      {
          ...
          FiringStatesArray(0)=WeaponFiring
          FiringStatesArray(1)=WeaponFiring
          ...
      }

      We do this in our base weapon class since we’ll likely use the same firing state in subclasses and we can change the value if we need to within those subclasses.

    4. Next, we need to define what kind of fire each mode will initiate i.e. The deafult WeaponFiring state class Weapon.FireAmmunition which checks the fire type of the current fire mode to run the actual firing logic. We do this in our specific weapon because this is where we know what fire type each mode actually has.
      class SKGravityGun extends SKWeapon;
      ...
      defaultproperties
      {
          ...	
          WeaponFireTypes(0)=EWFT_InstantHit
          WeaponFireTypes(1)=EWFT_None
      }

      I’ve set the secondary fire type to none because we don’t support it yet. There is logic within the Weapon.SendToFiringState function that aborts firing if the requested firing mode’s fire type is EWFT_None.

    5. And finally, this being a side scroller, we need to change the aim logic becuase the default one assumes the player aim where the camera is looking. In our sidescroller, the player will aim wherever the pawn is looking. When you fire the weapon, a trace is done to determine what the shot hit. The start of the trace is determined by calling Instigator.GetWeaponStartTraceLocation where instigator is the pawn. The trace’s direction is determined by calling Pawn.GetBaseAimRotation (it is called through other functions that are allowed to adjust the aim for things like auto aim, aim lock etc but all these are based on the direction returned by Pawn.GetBaseAimRotation). Lets overide these two functions.
      class SKPawn extends UDKPawn;
      
      /**
       * Start our traces form the pawn's eye which is what GetPawnViewLocation returns
       *
       * @return	World location where to start weapon fire traces from
       */
      simulated event Vector GetWeaponStartTraceLocation(optional Weapon CurrentWeapon)
      {
      	return GetPawnViewLocation();
      }
      
      /**
       * returns base Aim Rotation without any adjustment (no aim error, no autolock, no adhesion.. just clean initial aim rotation!)
       *
       * @return	base Aim rotation.
       */
      simulated singular event Rotator GetBaseAimRotation()
      {
      	local Rotator   POVRot;
      	local Int Modulo;
      
      	Modulo = Rotation.Yaw % 65535;
      	Modulo = Rotation.Yaw % 65535;
      
              /* Pulled this from the sidescroller framework on UDN - It basically makes sure that if the pawn's rotation
                 is between 90 and 270 degrees, we force the aim direction directly to the left and its not, we force it directly to
                 the right. This allows us to have smooth rotations on the pawn but keep the aiming to either left or right
              */
      
      	POVRot = Rotation;
      	if( (Rotation.Yaw % 65535 > 16384 && Rotation.Yaw % 65535 < 49560) ||
            (Rotation.Yaw % 65535 < -16384 && Rotation.Yaw % 65535 > -49560) )
      	{
      		POVRot.Yaw = 32768;
      	}
      	else
      	{
      		POVRot.Yaw = 0;
      	}
      
      	if( POVRot.Pitch == 0 )
      	{
                      // use the remote view pitch that is replicated to all clients and retrieve the quantized value as per -
                      //http://udn.epicgames.com/Three/NetworkingOverview.html#Variable type notes
      		POVRot.Pitch = RemoteViewPitch << 8;
      	}
      
      	return POVRot;
      }
      
      defaultproperties
      {
      }

      To visualize our aiming, I added a couple of variables and drew a debug line as follows:

      class SKWeapon extends UDKWeapon
      	abstract;
      	
      `if(`isdefined(Debug))
      var Vector DebugWeaponTraceStart;
      var Vector DebugWeaponTraceEnd;
      `endif
      
      ...
      
      state Active
      {
      	event Tick(float DeltaTime)
      	{
      		`if(`isdefined(Debug))
      		DebugWeaponTraceStart = Instigator.GetWeaponStartTraceLocation();
      		DebugWeaponTraceEnd = DebugWeaponTraceStart + vector(GetAdjustedAim(DebugWeaponTraceStart)) * GetTraceRange();
      		DrawDebugLine(DebugWeaponTraceStart, DebugWeaponTraceEnd, 255, 0, 0, False);
      		`endif	
      	}
      }
      ...
      

More information on weapons can be found on UDN at http://udn.epicgames.com/Three/WeaponsTechnicalGuide.html

Our game’s pawn is not derived from UTPawn which makes it hard to make use of the UTWeapon derived weapons.

Start by creating a base class for our weapons, derived from UDKWeapon.

class SKWeapon extends UDKWeapon
abstract;

defaultproperties

{

}

This will serve as the base class for all our weapons, hence its abstract. Lets now create a more specific weapon class.

class SKGravityGun extends SKWeapon;

defaultproperties
{
}

For quick setup and testing our weapon, lets add our new weapon as a default inventory item. When a Pawn is spawned by the game, the game calls Pawn.AddDefaultInventory, we’ll override this function (which is empty by default) and add our weapon using the Pawn.CreateInventory function.

/* AddDefaultInventory:
	Add Pawn default Inventory.
	Called from GameInfo.AddDefaultInventory()
*/
function AddDefaultInventory()
{
	local SKGravityGun GravityGun;

	// pass bDoNotActivate as False - the default to request activation of this inventory item
        GravityGun = SKGravityGun(CreateInventory(class'SKGravityGun', False));
	`log(GravityGun);
}

Attaching the weapon’s mesh

Our custom weapon is at this point in the pawn’s inventory, but you cant see it. Lets now add a skeletal mesh so we can see it in the pawn’s hand.

When a weapon is first activated, it is sent into the WeaponEquipping state which calls the Weapon.TimeWeaponEquipping function that is basically sets a timer that allows you to play the equip animation, attach meshes etc before the Weapon goes into an Active state.

The base Weapon class has an empty AttachWeaponTo function that we will override and call to run the attach logic.

simulated function AttachWeaponTo( SkeletalMeshComponent MeshCpnt, optional Name SocketName )
{
}

Then call the AttachWeapon function from within the TimeWeaponEquipping function.

/**
 * Sets the timing for equipping a weapon.
 * The WeaponEquipped event is trigged when expired
 */
simulated function TimeWeaponEquipping()
{
	// The weapon is equipped, attach it to the mesh.
	AttachWeaponTo( Instigator.Mesh );

	Super.TimeWeaponEquipping();
}

NOTE: Most of this is already implemented within the UT* classes and I’m basing this code of those.

Within our AttachWeaponTo function, we basically need to figure out which mesh to use as our weapon and determine which socket on our player to attach the weapon to.

The UT classes do it very well by having a separate class that holds the weapon’s skeletal mesh. This allows us to spawn this class when the weapon is equipped and destroy it when the weapon is un-equipped. Start by creating a SKWeaponAttachment base class then a subclass for our SKGravityGun.

class SKWeaponAttachment extends Actor
	abstract;

/** Weapon SkelMesh */
var SkeletalMeshComponent Mesh;

/**
 * Called on a client, this function Attaches the WeaponAttachment
 * to the Mesh.
 */
simulated function AttachTo(AcventurePawn OwnerPawn)
{
}

/**
 * Detach weapon from skeletal mesh
 */
simulated function DetachFrom( SkeletalMeshComponent MeshCpnt )
{
}

defaultproperties
{
	// Weapon SkeletalMesh
	Begin Object Class=SkeletalMeshComponent Name=SkeletalMeshComponent0
		bOwnerNoSee=true
		bOnlyOwnerSee=false
		CollideActors=false
		AlwaysLoadOnClient=true
		AlwaysLoadOnServer=true
		MaxDrawDistance=4000
		bForceRefPose=1
		bUpdateSkelWhenNotRendered=false
		bIgnoreControllersWhenNotRendered=true
		bOverrideAttachmentOwnerVisibility=true
		bAcceptsDynamicDecals=FALSE
		CastShadow=true
		bCastDynamicShadow=true
		bPerBoneMotionBlur=true
	End Object
	Mesh=SkeletalMeshComponent0
}

And the subclass to act as the attachment for the GravityGun

class SKGravityGunAttachment extends SKWeaponAttachment;
defaultproperties
{
    // Weapon SkeletalMesh
    Begin Object Name=SkeletalMeshComponent0
        SkeletalMesh=SkeletalMesh'WP_RocketLauncher.Mesh.SK_WP_RocketLauncher_3P'
    End Object
}

Within the AttachTo  functions we’ll attach to pawn’s mesh like this.

/**
 * Called on a client, this function Attaches the WeaponAttachment
 * to the Mesh.
 */
simulated function AttachTo(SKPawn OwnerPawn)
{
	if (OwnerPawn.Mesh != None)
	{
		// Attach Weapon mesh to player skelmesh
		if ( Mesh != None )
		{
			// Weapon Mesh Shadow
			Mesh.SetShadowParent(OwnerPawn.Mesh);
			Mesh.SetLightEnvironment(OwnerPawn.LightEnvironment);

			OwnerPawn.Mesh.AttachComponentToSocket(Mesh, OwnerPawn.WeaponSocket);
		}
	}
}

/**
 * Detach weapon from skeletal mesh
 */
simulated function DetachFrom( SkeletalMeshComponent MeshCpnt )
{
	// Weapon Mesh Shadow
	if ( Mesh != None )
	{
		Mesh.SetShadowParent(None);
		Mesh.SetLightEnvironment(None);
	}
	if ( MeshCpnt != None )
	{
		// detach weapon mesh from player skelmesh
		if ( Mesh != None )
		{
			MeshCpnt.DetachComponent( mesh );
		}
	}
}

We simply just attach the mesh within the attachment class to the pawn’s WeaponSocket. We need to define this socket variable on our pawn, which must exist on your pawn’s mesh and have a matching name i.e. WeaponPoint

class SKPawn extends UDKPawn;

...
/** WeaponSocket contains the name of the socket used for attaching weapons to this pawn. */
var name WeaponSocket;

...

defaultproperties
{
...
	// default bone names
	WeaponSocket=WeaponPoint

...
}

Next, we need to call this function, this is where it gets a bit interesting because of network replication. Start by defining a couple of variables, one to hold the class of the current attachment and another to old the actual attachment.

/** This holds the local copy of the current attachment.  This "attachment" actor will exist independantly on all clients */
var SKWeaponAttachment CurrentWeaponAttachment;

/** Holds the class type of the current weapon attachment.  Replicated to all clients. */
var repnotify class<SKWeaponAttachment> CurrentWeaponAttachmentClass;

The repnotify declaration tells the engine to call the pawn’s ReplicatedEvent function whenever this variable is updated. The idea behind this is that when running on a network, if a player on one computer equips a weapon, the engine on that player’s computer will send the updated value s to the server, which will in turn send it to other clients on the network. To get this to work, we have to:

      1. Tell the engine that we want the variable sent to the server whenever its updated
        class SKPawn extends UDKPawn;
        ...
        replication
        {
        	if ( bNetDirty )
        		CurrentWeaponAttachmentClass;
        }
        ...
      2. Tell the engine which function to call when this variable is updated.
        /**
         * Check on various replicated data and act accordingly.
         */
        simulated event ReplicatedEvent(name VarName)
        {
        
        	// If CurrentWeaponAttachmentClass has changed, the player has switched weapons and
        	// will need to update itself accordingly.
        	if ( VarName == 'CurrentWeaponAttachmentClass' )
        	{
        		WeaponAttachmentChanged();
        		return;
        	}
        	else
        	{
        		Super.ReplicatedEvent(VarName);
        	}
        }

You can read more on this on UDN – http://udn.epicgames.com/Three/NetworkingOverview.html#Replication

You’ll notice that we are calling a WeaponAttachmentChanged function which we have not defined yet, here it is.

/**
 * Called when there is a need to change the weapon attachment (either via
 * replication or locally if controlled.
 */
simulated function WeaponAttachmentChanged()
{
        // 1. None when we changed to empty hands, 2. compare classes so we dont change to the same weapon and 3. just make sure we have a skeletal mesh
	if ((CurrentWeaponAttachment == None || CurrentWeaponAttachment.Class != CurrentWeaponAttachmentClass) && Mesh.SkeletalMesh != None)
	{
		// Detach/Destroy the current attachment if we have one
		if (CurrentWeaponAttachment!=None)
		{
			CurrentWeaponAttachment.DetachFrom(Mesh);
			CurrentWeaponAttachment.Destroy();
		}
		// Create the new Attachment.
		if (CurrentWeaponAttachmentClass!=None)
		{
			CurrentWeaponAttachment = Spawn(CurrentWeaponAttachmentClass,self);
			CurrentWeaponAttachment.Instigator = self;
		}
		else
			CurrentWeaponAttachment = none;

		// If all is good, attach it to the Pawn's Mesh.
		if (CurrentWeaponAttachment != None)
		{
			CurrentWeaponAttachment.AttachTo(self);
		}
	}
}

And finally to tie it all together from the weapon class by updating the CurrentWeaponAttachmentClass variable for other clients on the network, while calling the function directly on the client that is actually making the change.

class SKWeapon extends UDKWeapon
	abstract;
...
/** The class of the attachment to spawn */
var class<SKWeaponAttachment> AttachmentClass;

...
/**
 * Attach Weapon Mesh
 *
 * @param	who is the pawn to attach to
 */
simulated function AttachWeaponTo( SkeletalMeshComponent MeshCpnt, optional Name SocketName )
{
	local SKPawn P;

	P = SKPawn(Instigator);
	// Spawn the 3rd Person Attachment
	if (Role == ROLE_Authority && P != None)
	{
		P.CurrentWeaponAttachmentClass = AttachmentClass;
		if (WorldInfo.NetMode == NM_ListenServer || WorldInfo.NetMode == NM_Standalone || (WorldInfo.NetMode == NM_Client && Instigator.IsLocallyControlled()))
		{
                        // call the function directly if we are the server, we're running standalone or we are the client that is changing our weapon
			P.WeaponAttachmentChanged();
		}
	}
}
...

Notice, we’ve added an AttachmentClass variable, each weapon subclass should set this to its attachment class so we know what to spawn.

class SKGravityGun extends SKWeapon;

defaultproperties
{
	AttachmentClass=class'SKGravityGunAttachment'
}

This time, I’m not providing a code listing so you can understand how all the pieces tie together by implementing it yourselves.