Weapon firing logic basically goes like this
- 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.
- 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 ' ... } ... - 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.
- 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.
- 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