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, if bAutomaticFire is set to true, will loop infinitely with a timer length of rateOfFire. If you're building a non-automatic weapon and have bAutomaticFire set to false, 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 of StartFire, our initial delay is always 0 so gunshots are always fired instantly when the player clicks.

  • void StopFire(); - StopFire is intrinsically linked to StartFire. It clears the timer that we set whenever StartFire 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 in StartFire at intervals of the rateOfFire 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 whole SWeaponBase class - casting to SCharacterController. 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 telltale CharacterController-> pointers as we access data within this class. We don't store this data within SWeaponBase, 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 within SWeaponBase 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 within SCharacter in order to prevent clutter in this class. More details on this can be found in the SCharacterController class overview. With the role of CharacterController summarised, let's actually get to the point 😛 . The first part of Fire is casting to CharacterController 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 and lengthMultiplier, where traceStart and traceStartRotation are derived from the meshComp socket of name muzzleSocketName. We then calculate the vector direction of the muzzle, derived from traceStartRotation, and essentially 'move it forward' by distance lengthMultiplier. 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)
  • 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 with UpdateAmmo, 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 call UpdateAmmo.

  • void UpdateAmmo(); - UpdateAmmo is a, conceptually at least, very simple function. Called after the animation we play in Reload finishes, UpdateAmmo, well, updates the ammunition values within SCharacterController, 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 for Fire 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 in StartFire 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. when bAutomaticFire is set to true). Set in Blueprint.

  • bool bCanBeChambered; - bCanBeChambered determines the functionality of the Reload 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 the meshComp found in your SCharacter 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 with bShowDebug (will draw the path of the line traces.

  • FVector traceStart; - Internal variable determined from the transform of the socket defined by muzzleSocketName

  • FRotator traceStartRotation; - Internal variable defined by the rotation of the socket muzzleSocketName

  • FVector traceDirection; - Internal variable calculated from traceStartRotation

  • FVector traceEnd; - Internal variable calculated from traceDirection, lengthMultiplier and traceStart, 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 the LineTraceByChannel 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 when headshotDamageSurface 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 the ApplyPointDamage function within Fire. 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 by headshotMultiplier). 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 and headshotDamageSurface). 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. If bWaitForAnim 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 totrue`.

  • FTimerHandle reloadingDelay;- Timer handle used to handle the delay between the Reload function and UpdateAmmo


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.