Posts Tagged ‘udk tutorials’

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.