Tutorials‎ > ‎UDK Melee Weapons‎ > ‎

UDK Melee Weapon Tutorial Part 2


Welcome Back!

This is the second part of the Melee Weapon tutorial. If you missed the first one, it would be a really good idea to go back and read it. In this part of the tutorial, I’ll talk about how weapons work in general in Unrealscript and show you how to tap into that system to get a melee weapon working. We’ll also open up the UDK editor and walk through creating archetypes and assigning their values. We’ll also look at how to trace the melee weapon’s swing and apply damage to the things it hits.

Archetypes

So up to this point, you’ve seen a lot of code and had a lot of explanation, but wouldn’t it be nice if we could run a level and actually see something happening? Well, you’re in luck because that’s what’s coming up next.

If you’ve never worked with archetypes before, they’re basically a way to bring actors/objects created in Unrealscript into a level through the editor. When you create an archetype, you can assign values to the attributes and properties associated with that class, and any instances of that archetype you put in the level will have those assigned properties. We’ve already set up the system to use those properties in our code, so whatever values we assign to the the archetype’s properties in the editor, those are the values that our code will use.

The first thing we need to do to use archetypes is create one. The easiest way to do this is to open up the content browser in the UDK editor and switch to the Actor Classes tab. Uncheck all the options at the top, and type in “MeleeWeapon” in the search bar. This will bring up all the MeleeWeapon classes we made in the last tutorial. We’re going to start with an archetype for our pawn, so right click on MeleeWeaponPawn and click on “Create Archetype…”:


A dialog box pops up and asks you where to save the archetype and what to name it. Back in our Default Properties section of our Game Info class, we said that our PawnArchetype would be found in ‘MeleeWeaponContent.Archetypes.MeleeWeaponPawn’, so that’s where we should put it. Hit OK then switch back to the content browser tab and find your brand new archetype. Double click it and you’ll see a properties window pop up. Expand the Melee Weapon Pawn section and you’ll see all the variables we made visible to the editor. Assign these values to the properties:


You’ll notice that we’re finally assigning a mesh to use: the Iron Guard mesh. We’re also adding in an AnimTree Template and an AnimSet, but we’ll be changing these later after we’ve created our own AnimSet and AnimTree. One thing you’ll want to change that isn’t in the image above is the z-translation on the mesh’s primitive component. If you leave it at default, your pawn will float above the ground, so change it to -29 to get his feet to touch the floor. As mentioned in the first part of this tutorial, we assign "WeaponPoint" (the name of the hand socket on all of the humanoid models that come with UDK) to the SwordHandSocketName property of the pawn. We also set our IsoCamAngle and CamOffsetDistance parameters here.

Now create an archetype for the MeleeWeaponSword class and assign the following values for the properties:


Fire Interval is how long to wait between shots. In the case of our sword, we’re going to let it swing twice with a quarter of a second between swings. Instant Hit Damage is the amount of damage the weapon will do and Instant Hit Momentum is the amount of force applied to the actor the sword hits. Remember that weapons in UDK are mostly guns, so they’re either going to fire an “Instant Hit” (ie: a bullet) or a projectile. When a weapon uses a projectile, the projectile class takes care of the damage and any knockback/momentum. Since our sword isn’t going to fire any projectiles (anyone ever play Crystalis? ;) ), we’ll use the Instant Hit properties to do our damage and knockback.

The skeletal mesh we’re using is looks a little to big to me, so in the primitive component section of the mesh, I scaled it down to 0.75. Also, you'll need to rotate the sword by -90 in the pitch in order to get it pointing up in the player's hand. Now when you play a level, you’ll see the iron guard running around with a sword attached to his weapon hand. Now it’s time to make him swing it.

UDK Weapons

I thought it would be helpful to know how weapons work in UDK before we move on. They’re pretty simple, but without understanding what happens from the time you click the mouse button to the time your weapon actually fires, the next steps could be a little confusing.

First of all, when you click the mouse button (or whichever button is bound to the firing function in the DefaultInput.ini file), it calls a series of functions that eventually ends up calling the weapon’s StartFire function. This function takes an integer as a parameter telling the weapon which “Fire Mode” it should be handling. This makes it possible to give weapons multiple firing modes (ie: shotgun with a grenade launcher attachment which can also be used as a melee weapon when your ammo runs out). Notice that the Fire Interval and Instant Hit parameters in our above archetype are arrays. That’s so that each fire mode can have different parameters.

There’s a chain reaction of functions that get called from there, but eventually what ends up happening is the Inventory Manager’s PendingFire array gets updated so that the slot at the FireMode index gets set to one. This PendingFire array is used to tell the weapon that a request to fire was sent. The weapon will check this array when it’s time to actually fire the weapon. If the weapon is in its “Active” state, which should be the case if the weapon is equipped, the weapon gets sent to the firing state stored in its FiringStatesArray variable at the FireMode index. This allows weapons to define different firing states for each firing mode.

The default firing state is WeaponFiring in the Weapon class. When this state begins, it sets a looping timer with a delay equal to the Fire Interval for the current Fire Mode to call the RefireCheckTimer function. So in the case of our sword, this RefireCheckTimer function should get called every 0.25 seconds. The RefireCheckTimer function does just what its name says: it checks to see if the weapon should fire again. This consists of checking to see if there’s still ammo and if the PendingFire array at the FireMode index is one, meaning that the weapon received a request to fire.

In the case of most weapons in UDK, PendingFire only gets cleared when the player lets go of the button, so as long as you hold down the button, PendingFire at that Fire Mode index will be one. Our sword will work a little differently – we’ll clear the PendingFire array at our Fire Mode index every time we swing. That way, the player must click the mouse button again within the Fire Interval to swing the second time. If the refire check fails, the weapon exits the WeaponFiringState and goes back into the Active state to wait for another request to fire.

Swinging the Weapon

Now that we understand how the weapon firing system works, we can use that infrastructure and manipulate it to swing the sword. There are a few variables we’re going to need, so put this at the top of your MeleeWeaponSword class:

var() const name SwordHiltSocketName; var() const name SwordTipSocketName; var array<Actor> SwingHitActors; var array<int> Swings; var const int MaxSwings;

We need the names of the sockets in the sword mesh for the hilt and the tip so we can run a trace between those two positions and see if it collides with anything. Notice that we're declaring these variables as editable so we can adjust their values in the archetype. We also need a list of actors that have been hit so we don’t hit them more than once in the same swing. The Swings array will represent our ammo, and the MaxSwings variable will be the maximum number of times we can swing the sword in a row. We’ll go ahead and assign some of these along with some other properties in our Default Properties:

DefaultProperties { MaxSwings=2 Swings(0)=2 bMeleeWeapon=true; bInstantHit=true; bCanThrow=false; FiringStatesArray(0)="Swinging" WeaponFireTypes(0)=EWFT_Custom ... }

We assign our firing states array at 0 to be our own state called “Swinging”, which we’ll write shortly. We’re also assigning the WeaponFireTipes array at 0 to be a Custom firing type. The weapon uses this array to determine what kind of “hit” to process. The options are EWFT_InstantHit, EWFT_Projectile, EWFT_Custom and EWFT_None. You might wonder why we aren’t using the Instant Hit fire type. Remember that an Instant Hit is usually associated with a gun that fires non-projectile ammo. We can’t use Instant Hit because we don’t want the Weapon class processing our hit as if our sword fired a bullet. We also can’t use the None fire type because the Weapon class completely skips over anything with that fire type.

Despite the fact that a sword is a hand-to-hand weapon, we’re going to treat each of the swings as if they were a “shot”, and we need to consume our “ammo” appropriately. Add these functions:

function RestoreAmmo(int Amount, optional byte FireModeNum) { Swings[FireModeNum] = Min(Amount, MaxSwings); } function ConsumeAmmo(byte FireModeNum) { if (HasAmmo(FireModeNum)) { Swings[FireModeNum]--; } } simulated function bool HasAmmo(byte FireModeNum, optional int Ammount) { return Swings[FireModeNum] > Ammount; } simulated function FireAmmunition() { StopFire(CurrentFireMode); SwingHitActors.Remove(0, SwingHitActors.Length); if (HasAmmo(CurrentFireMode)) { super.FireAmmunition(); } }

These should all be pretty self-explanatory. The only one I’ll comment on is the FireAmmunition function. We tap into the Weapon class’ FireAmmunition function to do two things: clear the SwingHitActors array (because we’re about to start our swing so any actors that got hit by the last swing are eligible to be hit again), and call the StopFire function, which will clear our PendingFire array at the CurrentFireMode index. The reason we do this is so that the player can’t just hold down the left mouse button and swing both times – we don’t wan’t auto-fire capability. It feels more intuitive with a melee weapon to click the button every time you want to swing. The ConsumeAmmo function gets called during the Weapon class' FireAmmunition function, so that's why we need to call it from within our own FireAmmunition function.

Next, let’s write our Swinging state. We’re going to extend the default WeaponFiring state, that way we get most of the functionality we already talked about and we only need to add a few things to handle our sword:

simulated state Swinging extends WeaponFiring { simulated event Tick(float DeltaTime) { super.Tick(DeltaTime); TraceSwing(); } simulated event EndState(Name NextStateName) { super.EndState(NextStateName); SetTimer(GetFireInterval(CurrentFireMode), false, nameof(ResetSwings)); } } function ResetSwings() { RestoreAmmo(MaxSwings); }

We only need to tap into what happens with each tick and what happens when we exit the state. When we’re done swinging, we want to restore our ammo, or in other words, enable the player to swing twice again, but we want to wait just a bit in order to allow the player to get back into the idle animation and to avoid constant slashing attacks. We set up a timer to restore our ammo after another fire interval passes. Also, on every tick, we want to trace our swing to see if our sword hit anybody this time.

Detecting a Hit

Finally, it’s time to write our TraceSwing function:

function Vector GetSwordSocketLocation(Name SocketName) { local Vector SocketLocation; local Rotator SwordRotation; local SkeletalMeshComponent SMC; SMC = SkeletalMeshComponent(Mesh); if (SMC != none && SMC.GetSocketByName(SocketName) != none) { SMC.GetSocketWorldLocationAndRotation(SocketName, SocketLocation, SwordRotation); } return SocketLocation; } function bool AddToSwingHitActors(Actor HitActor) { local int i; for (i = 0; i < SwingHitActors.Length; i++) { if (SwingHitActors[i] == HitActor) { return false; } } SwingHitActors.AddItem(HitActor); return true; } function TraceSwing() { local Actor HitActor; local Vector HitLoc, HitNorm, SwordTip, SwordHilt, Momentum; local int DamageAmount; SwordTip = GetSwordSocketLocation(SwordTipSocketName); SwordHilt = GetSwordSocketLocation(SwordHiltSocketName); DamageAmount = FCeil(InstantHitDamage[CurrentFireMode]); foreach TraceActors(class'Actor', HitActor, HitLoc, HitNorm, SwordTip, SwordHilt) { if (HitActor != self && AddToSwingHitActors(HitActor)) { Momentum = Normal(SwordTip - SwordHilt) * InstantHitMomentum[CurrentFireMode]; HitActor.TakeDamage(DamageAmount, Instigator.Controller, HitLoc, Momentum, class'DamageType'); } } }

There’s a lot going on here, but most of it should be pretty easy to understand. We’re simply running a trace from the sword’s hilt to the sword’s tip. If this trace intersects with any actors, we check to see if that actor has already been hit by this swing with the AddToSwingHitActors function. We calculate the momentum vector by using the sword’s hilt and tip again and give it a magnitude of the Instant Hit Momentum property we assigned in the archetype. We pass in all the info we calculated to the actor’s TakeDamage function, which handles damaging the actor and throwing them in the direction of the momentum.

There’s only one thing left to do now to get our TraceSwing function to work: we need to go back into our MeleeWeaponSword archetype in the editor and assign some names to the SwordHiltSocketName and SwordTipSocketName. These variables weren’t available the first time because we hadn’t put them in the class yet. Now that they’re there, all we need to do is rebuild the scripts and they’ll show up in the properties window:


If you open up the socket manager on the sword’s mesh, you’ll see that there’s a socket called StartControl on the hilt and one called EndControl on the tip, so those are the ones we’ll use.

Time to test out the functionality of the code. Delete the AnimSet from the MeleeWeaponPawn archetype so that the pawn just floats around with the sword sticking straight out in front of him. To watch the sword hit something, create a level (if you’re using PSMU, you can just open up the MeleeWeaponMap that was created when you added the Tutorial suite) and add a few Rigid Body Actors to it. If you don’t know how to add a Rigid Body Actor to a level, an easy way to do it is:

  • Find a static mesh in the Content Browser that has a collision model (like the RemadePhysBarrel) and select it
  • Right click in your level and go down to Add Actor -> All Templates -> Add Rigid Body
  • Scroll down to the Static Mesh and click the green arrow to pull in the static mesh you selected and click OK

Now play the level and walk up to the static mesh you put in. Make sure the sword is colliding with the static mesh and click the left mouse button. If you’ve done everything right, you should see the static mesh move away from you as the momentum vector pushes it in the direction of the sword. If it doesn’t move very far, you can try tweaking the Instant Hit Momentum property in the MeleeWeaponSword archetype.

As awesome as that is, I’m sure you want your pawn to actually physically swing the sword. In part three of this tutorial, we’ll cover how to add your own custom animations to the UDK. I’ll show you how to import an animation into a new AnimSet and we’ll create a new AnimTree and show you how to access the nodes inside from within your code to play an animation on cue. We’ll go over how to spawn particle effects when you swing your sword and how to play a sound effect when you hit something with it.