UDN
Search public documentation
WeaponsTechnicalGuide
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.
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:
- 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.
- 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.
- 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()
- 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().
- 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.
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.- The InventoryManager (IM) on the Local Client receives a StartFire call. It calls StartFire().
- If Local Client is not Authoritive it notifies the server via ServerStartFire().
- Both StartFire() and ServerStartFire() sync up by calling BeginFire().
- BeginFire sets the PendingFire flag for the incoming fire Mode
- 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.
- The Firing Logic is handled in the various firing states. Firing states are responsible for the following:
- Continuing to fire if their associated PendingFire is hot
- Transitioning to a new weapon when out of ammo
- Transitioning to the "Active" state when no longer firing
- The IM on the Local Client calls StopFire().
- If Weapon Stop fire is not on the Authoritive process, it notifes the server via the ServerStopFire() event.
- Both StopFire() and ServerStopFire() sync up by calling EndFire().
- EndFire() clears the PendingFire flag for this outgoing fire mode.
/**
* 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.
