SWeaponBase class overview - whoisEllie/Project-Isolation GitHub Wiki
SWeaponBase
Overview
SWeaponBase
is, as the name suggests, the base weapon class in Project Isolation. It's intended to be used as a platform to build custom weapons off of, featuring a ton of customisable parameters. It's built for modularity and flexibility, and should hold up for all kinds of weapons, from snipers to shotguns and flintlocks to blasters.
Functions
-
void StartFire();
- In order to allow for the possibility of automatic fire, we employ a start/stop system for firing, rather than passing a click directly into a fire function.StartFire
spawns a timer which, ifbAutomaticFire
is set totrue
, will loop infinitely with a timer length ofrateOfFire
. If you're building a non-automatic weapon and havebAutomaticFire
set tofalse
, the timer will not loop. In Unreal we can specify 2 values for timers - the delay between executions and an initial delay. In the case ofStartFire
, our initial delay is always 0 so gunshots are always fired instantly when the player clicks. -
void StopFire();
-StopFire
is intrinsically linked toStartFire
. It clears the timer that we set wheneverStartFire
is called, meaning that the gun stops looping its fire function and hence stops firing. -
void Fire();
-Fire
is where we actually handle all the firing logic. It's called from the timer inStartFire
at intervals of therateOfFire
variable.Fire
handles a lot of things, so let's go through them one-by-one.- Checking if we can fire - The first important element of our
Fire
function is of course making sure that we can actually fire. This is also the first instance of something that will be rather common in the wholeSWeaponBase
class - casting toSCharacterController
.SCharacterController
stores a lot of data for our weapon, as you'll see if you take a look through the code and look for the telltaleCharacterController->
pointers as we access data within this class. We don't store this data withinSWeaponBase
, as logical as it may seem, because every time the player swaps weapons, their previously equipped weapon is destroyed, and the next time we spawn it all its variables are returned to their defaults. Say we have a default value of 7 rounds of ammunition loaded into the gun. Having this value stored withinSWeaponBase
would mean that rather than reloading, the player would simply be able to switch weapons, then switch back to our original weapon and have 7 rounds loaded again. Similarly, Project Isolation features weapon health. We don't want the player to be able to reset the weapon's health value simply by switching weapons. We don't store data withinSCharacter
in order to prevent clutter in this class. More details on this can be found in the SCharacterController class overview. With the role ofCharacterController
summarised, let's actually get to the point 😛 . The first part ofFire
is casting toCharacterController
so we have a local reference, and then we check to see if the player can fire, is not reloading, and if there is ammunition in the clip (weaponParameters.clipSize
). - Now that we know that we can fire, we can set up the parameters we need to successfully execute the function. This takes the form of vector calculations. We start with 3 variables -
traceStart
,traceStartRotation
andlengthMultiplier
, wheretraceStart
andtraceStartRotation
are derived from themeshComp
socket of namemuzzleSocketName
. We then calculate the vector direction of the muzzle, derived fromtraceStartRotation
, and essentially 'move it forward' by distancelengthMultiplier
. This leaves us with a start point (traceStart
) and an end point (traceEnd
) between which to draw our line trace. This is also where we subtract one from our weapon's loaded ammunition. - Finally, we can actually fire the shot. We do this using a line trace, executed with unreal's
LineTraceSingleByChannel()
function. We call this within the condition of the if statement as there's no point in executing all the following code (such as applying damage)
- Checking if we can fire - The first important element of our
-
void Reload();
-Reload
is not as explicit as it might first appear. This is not the function where we actually update the weapon's ammunition. This is down to our weapon animations - we want these to play before we actually update the ammunition in the weapon, so that if the player switches weapons during a reload (it's best not to restrict this, so players can switch weapons when they need to) their weapon isn't reloaded. Essentially, we don't want the player to be able to press reload, have the ammunition updated, and then play the reload animation, as this lets the players press reload, and then potentially swap weapons without waiting for the animation to finish, but still ending up with a reloaded weapon. We combat this withUpdateAmmo
, which is called after we check if we are not reloading, if a reloading montage exists, and if there is any point in reloading (player's ammo isn't full). After that, we play an animation, set a timer based on its length, and callUpdateAmmo
. -
void UpdateAmmo();
-UpdateAmmo
is a, conceptually at least, very simple function. Called after the animation we play inReload
finishes,UpdateAmmo
, well, updates the ammunition values withinSCharacterController
, and hence, within the weapon itself (which accesses those values). This gains complexity when we introduce the ability for guns to have chambered rounds or not - If they do, we raise the maximum amount of bullets in the gun to 1 more than the regular value, but the amount the player can reload in one go is still just the regular value, meaning reloading from empty won't allow the player to load the whole clip plus a shot in the chamber, as it would in real life. -
void EnableFire();
-EnableFire
is a small function we can call to after reloading finishes to enable the player's ability to fire again.
Variables
-
bool bCanFire;
- The variable we use to keep track of whether the player can fire or not, this must be true forFire
to execute. -
bool bAutomaticFire;
-bAutomaticFire
determines whether the weapon will be automatic, or use a semi-auto/not-auto (what's the proper name for that?) system. If it's true, the timer we set inStartFire
will be set to looping. Set in Blueprint. -
bool bIsReloading;
- A simple variable to keep track of whether the weapon is currently in its reloading state or not. -
float rateOfFire;
-rateOfFire
determines the time between shots on an automatic weapon (i.e. whenbAutomaticFire
is set to true). Set in Blueprint. -
bool bCanBeChambered;
-bCanBeChambered
determines the functionality of theReload
function. If a weapon can be chambered, it allows it to hold one bullet more than its magazine capacity (simulating a bullet being in the chamber). In the case of a chambered weapon (bCanBeChambered
is true), the player will reload to a full clip if the weapon is empty, and will reload to a full clip + 1 if there is at least 1 bullet still in the weapon. If this is turned off, then the weapon reloads 'normally', i.e. has a maximum clip size, and each reload will fill that up as much as possible with the remaining ammunition. -
FName weaponAttachmentSocketName;
- A necessary variable, this determines which socket on themeshComp
found in yourSCharacter
blueprint the weapon will attack to. Set in Blueprint. -
FName muzzleSocketName;
- The name of the socket located at the muzzle, this is used for muzzle flash particle effects and the spawning of the line trace. Make sure this is oriented correctly, otherwise the line trace will spawn facing up/down and the weapon will not be usable. Can be debugged withbShowDebug
(will draw the path of the line traces. -
FVector traceStart;
- Internal variable determined from the transform of the socket defined bymuzzleSocketName
-
FRotator traceStartRotation;
- Internal variable defined by the rotation of the socketmuzzleSocketName
-
FVector traceDirection;
- Internal variable calculated fromtraceStartRotation
-
FVector traceEnd;
- Internal variable calculated fromtraceDirection
,lengthMultiplier
andtraceStart
, determines the end point of the line trace. -
float lengthMultiplier;
-lengthMultiplier
determines the range of the weapon (or the length of the ray trace used to deal damage). Typically, this is set to an arbitrarily large value, but can also be set to low values if you'd like to limit the range of your weapons in any way. Set in Blueprint. -
FCollisionQueryParams queryParams;
- An internal variable that's passed in to theLineTraceByChannel
function. -
FHitResult hit;
- A struct that contains details about the hit point of the line trace. We use it to obtain information such as the actor hit by the line trace (to apply damage) or the hit physical material (to determine the damage multiplier and hit particles). -
float baseDamage;
- The base amount of damage that the weapon deals without any modifiers applied. Set in Blueprint. -
float headshotMultiplier;
- The multiplier applied to the weapon whenheadshotDamageSurface
is hit. Set in Blueprint -
float finalDamage;
- Internal variable used to keep track of the final damage value. -
TSubclassOf<UDamageType> damageType;
- The damage type used in theApplyPointDamage
function withinFire
. Set in Blueprint. -
UPhysicalMaterial* normalDamageSurface;
- The physical material for the normal damage surface. Set in Blueprint. -
UPhysicalMaterial* headshotDamageSurface;
- The physical material for the headshot damage surface (leads to damage being multiplied byheadshotMultiplier
). Set in Blueprint. -
UPhysicalMaterial* groundSurface;
- The physical material for the ground (leads to alternative hit particle effects being played). Set in Blueprint. -
UPhysicalMaterial* rockSurface;
- The physical material for rocks (leads to alternative hit particle effects being played). Set in Blueprint. -
UNiagaraSystem* enemyHitEffect;
- A reference to the niagara particle system to be played when an enemy is hit (normalDamageSurface
andheadshotDamageSurface
). Set in Blueprint. -
UNiagaraSystem* groundHitEffect;
- A reference to the niagara particle system to be played when the ground is hit (groundSurface
). Set in Blueprint. -
UNiagaraSystem* rockHitEffect;
- A reference to the niagara particle system to be played when a rock is hit (rockSurface
). Set in Blueprint. -
UNiagaraSystem* defaultHitEffect;
- A reference to the niagara particle system to be played when no defined physical material is hit. Set in Blueprint. -
float animTime;
- An internal variable used to store the amount of time a reloading animation takes, so that we can refill the ammunition variables as soon as it has finished. -
bool bWaitForAnim;
-bWaitForAnim
determines whether or not we wait for an animation to finish during the firing process. This can be used, for example, for sniper rifles which use a bolt to chamber new bullets. In this situation the firing animation would include the character firing the weapon, then pulling back the bolt and chambering another round. IfbWaitForAnim
is checked, the player would only be able to fire the weapon after this animation finishes. Set in Blueprint. -
UAnimMontage* fireMontage;
- The animation montage that plays on the weapon every time it is fired. This can be something like the bolt sliding back and then forwards, or the bob of certain loose elements of the weapon. Set in Blueprint. -
UAnimMontage* reloadMontage;
- The animation montage that plays every time the weapon is reloaded with a bullet in the chamber (and hence a bullet does not have to be chambered). Set in Blueprint. -
UAnimMontage* emptyReloadMontage;
- The animation montage that plays every time the weapon is reloaded from being fully empty. Set in Blueprint. -
float weaponDegredationRate;
- The amount of damage dealt to a weapon every time it is fired. Set in Blueprint. -
FTimerHandle shotDelay;
- Timer handle used to handle automatic firing. -
FTimerHandle animationWaitDelay; - Timer handle used to handle allowing the player to fire again if
bWaitForAnimis set to
true`. -
FTimerHandle reloadingDelay;
- Timer handle used to handle the delay between theReload
function andUpdateAmmo
Uses
SWeaponBase
is the foundational platform for any weapons you may wish to build. It's flexible and adaptable, able to be used for a wide range of weapons. It's designed to act as a parent class - any new weapons you'd like to make can be created as a child of this class, and then have their variables set accordingly. Note that this class is not yet fully complete - it needs to gain support for shotgun-type weapons, a dynamic system to interface with SCharacterController
and the ability to call animations in SCharacter
.