UDN
Search public documentation

WeaponsTechnicalGuide
Licensees can log in.

Red links require licensee log in.

Interested in the Unreal engine?
Check out the licensing page.

Questions about UDN itself?
Contact the UDN Staff

Weapon System Technical Guide

Document Summary: A technical guide to the current weapon system.

Document Changelog: Created by Joe Wilcox.

Overview

This document covers the default weapon system as found in Unreal Engine 3. Its purpose is to give you a quick overview of how the weapon system works and how easy it is to create a new weapon. I will begin by looking at how the weapon interacts with a pawn in terms of inventory control including giving a weapon to a pawn, selecting and discarding weapons. This will be followed by a discussion of how the firing sequence work.

Inventory Management

A Weapon is a child of Inventory, so there are two ways for it to end up in a pawns inventory. It's either given to the pawn directly using the Pawn.GiveWeapon() function or it's assigned to the pawn via a DroppedPickup or PickupFactory actor. DroppedPickups are actors that get created when a weapon is placed in the world and represents the Weapon's physical presence. They are created when a player throws out an item (either intentionally or upon death) in the Inventory.DropFrom(). The PickupFactory actor usually represents the actor placed in a map by a designer. These actors handle the task of deciding if there is an inventory item to give and are responsible for its state in the world.

Both the DroppedPickup and the PickupFactory take an existing Inventory item and give it to a pawn. This is perform through the Item.GiveTo() function. This function tells the InventoryManager to add this item to a pawn's inventory via the InventoryManager.AddInventory() function.

The second method for giving a weapon to a pawn is through Pawn.GiveWeapon(). This is a helper function that allows non-physical actors (such as the GameInfo or a Mutator) to easily give a weapon to a pawn without having to spawn one itself. It does this by calling the InventoryManager.CreateInventory() passing in the class of the weapon to create.

Once your weapon has been given to a pawn, you will need to activate it before it can be used. Typically, activation either comes via user input, typically through the PlayerController.NextWeapon() or PlayerController.PrevWeapon() and InventoryManager.SwitchToWeaponClass() exec functions. These functions perform some logic and when a new weapon is decided upon, call InventoryManager.SetCurrentWeapon().

SetCurrentWeapon() is the main starting point for activating a weapon. It takes 1 parameter, DesiredWeapon which should be a reference to the weapon you want to activate and it should always be called from a local client. Once called, it initiates the chain of code that causes the current weapon to be put down (returned to inventory and deactivated). Figure 1 gives a visual overview of how the system works.

figure1.jpg

The InventoryManager uses the PendingWeapon property to determine which weapon to switch to when the putdown sequence is finished. SetCurrentWeapon() sets the property and then tells then calls Weapon.TryPutDown(). This function initiates the deactivation of the weapon.

Weapon.TryPutDown() should attempt to change in to the WeaponPuttingDown state. If it's not a good time, the weapon sets the bWeaponPutDown flag property. This property is checked in most transitions to see if a put down has been requested. If TryPutDown() is successful in either entering the WeaponPuttingDown state or at setting the bWeaponPutDown flag, it should return true. Other it returns false.

If the attempt at putting the weapon down is successful, SetCurrentWeapon() calls ServerSetCurrentWeapon() to initiate the process on the server. This process is nearly an identical to that in SetCurrentWeapon(), however, two things need to be considered. First, ServerSetCurrentWeapon() assumes that the TryPutDown() succeeded on the client. Second, it has to take in to account listen servers and restricts the server-side calling of TryPutDown() if this InventoryManager is locally controlled.

All of the above assumes that there is an existing weapon. If not, TryPutDown() is skipped and control is passed directly to ChangedWeapon(). Before we look ChangedWeapon(), it's important to look at the WeaponPutingDown and the other weapon states.

There are six important states to a weapon. They are:

  1. Inactive - Typically, a weapon is in the inactive state when it's not in use. By default, this means the weapon has been hidden and it will no longer get StartFire()/EndFire() events.
  2. Active - When a weapon is in hand, but not firing it is in the active state. A call to StartFire() from this state initiates the firing sequence.
  3. WeaponEquipping - When you first activate a weapon (by calling it's Activate() function) it will enter the WeaponEquipping state. Here it should play any "bring up" animations and effects. The time in which it takes to become active is controlled by TimeWeaponEquipping()
  4. WeaponPuttingDown - This states defines a weapon as it's being put down and deactivated. It's typically entered from the TryPutDown() function or by a delayed check of bWeaponPuttingDown. Like WeaponEquipping, it should play any needed animations and effects and time the action using TimeWeaponPutDown().
  5. PendingClientWeaponSet - This is a special state that will only ever be entered in to on the client. A weapon enters this state when it's been activated, but it's initial and important information has not yet been replicated (such as instigator or owner). Once in this state, the weapon will set interval check to see that these values have come in. At that point, it will continue to the WeaponEquipping state.

With the exception of PendingClientWeaponSet, the weapon will run the various states in parallel on both a client and the server, branching when needed, however there will be more on this later.

Once a weapon has been put down, InventoryManager.ChangedWeapon() is called (from the WeaponIsDown() function of the weapons WeaponPuttingDown state. ChangedWeapon is responsible for the actual changing of the weapon property in pawn and making any notifications. Finally it activates the new weapon.

This occurs, again in parallel on the client and the server. This is needed in order to avoid a visual delay as the server catches up. You will see that the weapon code handles this gracefully using the PendingFire system when we get there.

Once Weapon.Activate() is called, the weapon will transition in to the WeaponEquipping state. From the list above, we see that this state is responsible for initializing the weapon for use and playing any bring up animations or effects. The `equipping' of the weapon is controlled by the TimeWeaponEquipping() function. It's from inside this function that the Weapon's Mesh is attached and any animations are played. Finally, a timer is set for EquipTime and when it expires, the WeaponEquipped() function is called. Once equipped the weapon transitions to the Active state and is ready to use.

The Firing Sequence

Once we have an active weapon, this weapon can begin receiving firing events from the Player controller. Before you look at the firing code path, you should review the variable declarations in Weapon.uc as well as the inventory functions like HasAmmo(). By default, the UE3 weapon system contains only stub functions for handling ammunition and the actual implementation is left to the game.

Below is a comment block from Weapon.uc that gives a quick overview of the firing sequence.

Weapon Firing Logic

The weapon system here is designed to be a single code path that follows the same flow on both the Authoritive server and the local client. Remote clients know nothing about the weapon and utilize the WeaponAttachment system to see the end results.

  1. The InventoryManager (IM) on the Local Client receives a StartFire call. It calls StartFire().
  2. If Local Client is not Authoritive it notifies the server via ServerStartFire().
  3. Both StartFire() and ServerStartFire() sync up by calling BeginFire().
  4. BeginFire sets the PendingFire flag for the incoming fire Mode
  5. BeginFire looks at the current state and if it's in the Active state, it begins the firing sequence by transitioning to the new fire state as defined by the FiringStatesArray array. This is done by calling SendToFiringState.
  6. The Firing Logic is handled in the various firing states. Firing states are responsible for the following:
    1. Continuing to fire if their associated PendingFire is hot
    2. Transitioning to a new weapon when out of ammo
    3. Transitioning to the "Active" state when no longer firing

The weapon system also receives a StopFire() event from the IM. When this occurs, the following logic is performed:

  1. The IM on the Local Client calls StopFire().
  2. If Weapon Stop fire is not on the Authoritive process, it notifes the server via the ServerStopFire() event.
  3. Both StopFire() and ServerStopFire() sync up by calling EndFire().
  4. EndFire() clears the PendingFire flag for this outgoing fire mode.

Firing states should be identical in their execution, branching outwards as need. For example, in the default firing state ('WeaponFiring') the function FireAmmunition() occurs in all applicable processes.

That's a lot to take in, so let's look at the entire sequence in more detail, and we begin at the PlayerController. When the PlayerController receives a StartFire() exec command, it tells the pawn. The pawn tells the InventoryManager and the InventoryManager begins the full sequence by calling Owner.Weapon.Start(). The playerController is responsible for recognizing which fire mode the end user is looking to use and pass that along. In time, the property Weapon.CurrentFireMode will be set.

Once StartFire() is called, the weapon does a quick Ammo check and if it fails, it attempts to switch to a better weapon. If it doesn't fail, it calls BeginFire(). Additionally, if StartFire() was initiated on a remote client, then the requested is replicated to the server via ServerStartFire(), which in turn calls BeginFire(). Weapon.BeginFire() is where the weapon begins to run in parallel on both the client and server. From here, you will see that the code path will only diverge as needed to handle effects/etc. In most states, all BeginFire() does is make sure the PendingFire byte flag for the selected Firing mode is set.

Only from the Active state will the weapon begin the firing sequence. This occurs with a call to Weapon.SendToFiringState(). This function will transition the weapon to the state WeaponFiring. Upon entering the new state, the weapon will immediately attempt to take a shot by calling the FireAmmunition() function. It then sets up the timing for the firing sequence by calling TimeWeaponFiring().

By default, the FireAmmunition() function is very simple and is listed below:

/**
 * FireAmmunition: Perform all logic associated with firing a shot
 * - Fires ammunition (instant hit or spawn projectile)
 * - Consumes ammunition
 * - Plays any associated effects (fire sound and whatnot)
 *
 * Network: LocalPlayer and Server
 */

simulated function FireAmmunition()
{
   // Use ammunition to fire
   ConsumeAmmo( CurrentFireMode );

   // if this is the local player, play the firing effects
   PlayFiringSound();

   // Handle the different fire types
   switch( WeaponFireTypes[CurrentFireMode] )
   {
      case EWFT_InstantHit:
         InstantFire();
         break;

      case EWFT_Projectile:
         ProjectileFire();
         break;

      case EWFT_Custom:
         CustomFire();
         break;
   }
}

The FireAmmunition() function is responsible to initiate the actual "shot" and refers to the WeaponFireTypes entry for the current fire mode. If the weapon is an instant hit weapon (EWFT_InstantHit) then TraceFire() is called. TraceFire() is responsible for tracing a shot and managing any hits. For weapons that launch projectiles (EWFT_Projectile) the ProjectileFire() function is called. This function is responsible for spawning and initializing the projectile. Finally, if it is a custom firing type (EWFT_Custom) then the CustomFire() function is called.

TimeWeaponFiring() sets a timer that will trigger the RefireCheckTimer() function at the given firing interval for the current fire mode. RefireCheckTimer() will check to see if a state transition is needed, or fire an additional shot. State transitions are needed if the if the weapon is out of ammo or if the PendingFire byte flag for the current firing mode is no longer set. The function ShouldRefire() handles the logic to decide if the weapon should keep on firing, or abort and go to the Active state.

Like starting the firing sequence, stopping the sequence begins at the PlayerController and follows an also identical path. The PlayerController notifies the Pawn who notifies the InventoryManager who notifies the current weapon. The weapon's StopFire() then splits to the ServerStopFire() if needed and finally we end with EndFire() clearing the PendingFire byte flags.