Spread System
This section covers how to create, edit, and utilize spread patterns in your game.
The Spread System controls projectile dispersion patterns during weapon fire, creating dynamic accuracy effects based on player actions and weapon state. Configure spread behavior through curves, heat mechanics, and modifiers.
Creating a New Spread Pattern
In the Content Browser, right-click and select Recoil & Spread → Spread Pattern
Name your pattern (e.g., "B_Spread_Rifle")
Double-click to open the pattern in the custom editor
Spread Pattern Editor

The Spread Pattern Editor is a specialized tool within Unreal Engine that allows designers to visually create and fine-tune weapon spread behavior. It simulates how bullets disperse around the crosshair based on heat accumulation, player actions, and environmental factors.
1. Core Spread Pattern Parameters
The spread pattern model allows you to precisely control the dispersion of bullets before the gun is fired. You can adjust these basic parameters according to your needs.
Enable Spread
Toggles spread mechanics on/off
Spread Exponent
Controls shot clustering:
1.0
= Uniform spread, >1.0
= Center-biased
Heat → Spread Curve
Maps heat (X-axis) to spread angle (Y-axis)
Heat → Heat Per Shot Curve
Defines heat added per shot at current heat level
Heat → Cooldown Curve
Sets heat decay rate (units/sec) at current heat
Recovery Cooldown Delay
Delay (sec) before heat decay starts after firing
First Shot Accuracy
Enables perfect accuracy at min heat/spread
2. Heat System Mechanics
Simulates weapon "overheating" to drive spread progression.
Key Concepts
Heat: Internal value (0–MaxHeat) representing weapon instability
Heat Gain:
NewHeat = CurrentHeat + HeatPerShotCurve(CurrentHeat)
Heat Decay:
NewHeat = CurrentHeat - (CoolDownCurve(CurrentHeat) * DeltaTime)
Workflow Example
Initial State: Heat=0 → Spread=2°
Fire 3 Shots:
Heat increases via
HeatPerShotCurve
→ Heat=25 → Spread=8°
Stop Firing:
After 0.5s delay, heat decays via
CoolDownCurve
3. Spread Calculation
Final spread angle combines multiple factors:
EffectiveSpread = HeatToSpreadCurve(CurrentHeat) * SpreadMultiplier
Components
Base Spread
HeatToSpreadCurve
Y-min
Max Spread
HeatToSpreadCurve
Y-max
Spread Multiplier
Combined effect of active modifiers
4. Spread Modifiers
Spread modifiers allow you to dynamically change spread behavior based on gameplay conditions. You can define your spread modifiers by using the UARSSpreadModifier
class or other built-in modifiers. You can derive your class in both C++ or Blueprint.
Base Spread Modifier Class:
UCLASS(Abstract, BlueprintType, Blueprintable, EditInlineNew, DefaultToInstanced)
class ADAPTIVERECOILSPREAD_API UARSSpreadModifier : public UObject
{
GENERATED_BODY()
public:
/**
* Modifies the spread multiplier.
*
* @param DeltaTime - The time elapsed since the last frame, used for smooth interpolation of spread changes.
* @param BaseSpreadMultiplier - The default spread multiplier without any modifications.
* @param CurrentSpreadMultiplier - The current spread multiplier after applying previous modifications.
* @param SpreadPattern - The spread pattern asset used for determining spread behavior.
* @param RecoilSpreadManager - Owning Recoil and Spread Manager component from the controller
* @return - The modified spread multiplier.
*/
UFUNCTION(BlueprintNativeEvent)
float ModifySpreadMultiplier(float DeltaTime, float BaseSpreadMultiplier, float CurrentSpreadMultiplier, UARSSpreadPattern* SpreadPattern, UARSRecoilSpreadManager* RecoilSpreadManager);
virtual float ModifySpreadMultiplier_Implementation(float DeltaTime, float BaseSpreadMultiplier, float CurrentSpreadMultiplier, UARSSpreadPattern* SpreadPattern, UARSRecoilSpreadManager* RecoilSpreadManager);
// Returns whether the multiplier is at its minimum value
UFUNCTION(BlueprintNativeEvent)
bool IsAtMinMultiplier() const;
virtual bool IsAtMinMultiplier_Implementation() const;
static constexpr float Default_Multiplier = 1.0f;
Built-in Modifier Types
There are multiple spread modifiers natively implemented in C++. You can directly use these modifiers or create your own modifiers derived from them:
UARSSpreadModifier_Crouch
Jump/Fall
+Spread
during airborne states
UARSSpreadModifier_JumpOrFall
Crouch
-Spread
when crouching
UARSSpreadModifier_StandingStill
Movement
+Spread
scaling with velocity
6. Debug Visualization
There is an auto enabled in-editor debugging system in Spread Pattern editör. Below debug values are calculated dynamically when you change pattern parameters.
Debug_Min/MaxHeat
Heat range boundaries
Debug_CurrentHeat
Real-time heat value
Debug_CurrentSpread
Live spread angle
7. Best Practices
Curve Design:
Keep
Heat→SpreadCurve
X-range matchingHeatPerShot
accumulation
Modifier Stacking:
Order modifiers by priority (e.g., movement before stance)
Network Sync:
Networking is automatically handled by replicating necessary values for multiplayer consistency.
Example Configuration
Assault Rifle:
- Base Spread: 2.0°
- Max Spread: 12.0°
- Heat Decay: 15 units/sec
- Modifiers:
- Crouch (0.7x)
- Jump (1.8x)
Advanced
Creating Custom Modifiers
Create your own spread modifiers by:
Extending the
UARSSpreadModifier
classImplementing the required modification methods
Adding your modifier to the weapon component
Below is an example of the built-in UARSSpreadModifier_JumpOrFall
spread modifier:
Implementation Steps
1. Header File Implementation (UARSSpreadModifier_JumpOrFall.h
)
UARSSpreadModifier_JumpOrFall.h
)#pragma once
#include "ARSSpreadModifier.h"
#include "ARSSpreadModifier_JumpOrFall.generated.h"
/**
* Spread modifier that adjusts the spread multiplier when the character is jumping or falling
*/
UCLASS()
class ADAPTIVERECOILSPREAD_API UARSSpreadModifier_JumpOrFall : public UARSSpreadModifier
{
GENERATED_BODY()
public:
virtual float ModifySpreadMultiplier_Implementation(float DeltaTime, float BaseSpreadMultiplier, float CurrentSpreadMultiplier, UARSSpreadPattern* SpreadPattern, UARSRecoilSpreadManager* RecoilSpreadManager) override;;
virtual bool IsAtMinMultiplier_Implementation() const override;
protected:
// Spread multiplier while jumping/falling, smoothly blended to based on TransitionRate_JumpingOrFalling
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Spread Params", meta=(ForceUnits=x))
float SpreadAngleMultiplier_JumpingOrFalling = 1.0f;
// Rate at which we transition to/from the jumping/falling accuracy (higher values are faster, though zero is instant; @see FInterpTo)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Spread Params")
float TransitionRate_JumpingOrFalling = 5.0f;
private:
// Current jumping/falling spread multiplier
float CurrentJumpFallMultiplier = 1.0f;
// Whether the multiplier is at its minimum value
bool bIsAtMinMultiplier = false;
// Multiplier threshold for nearly equal comparison
const float MultiplierNearlyEqualThreshold = 0.05f;
};
2. Source File Implementation (ARSSpreadModifier_JumpOrFall.cpp
)
ARSSpreadModifier_JumpOrFall.cpp
)#include "Modifiers/ARSSpreadModifier_JumpOrFall.h"
#include "ARSLogChannels.h"
#include "Components/ARSRecoilSpreadManager.h"
#include "Core/ARSSpreadPattern.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/Pawn.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(ARSSpreadModifier_JumpOrFall)
float UARSSpreadModifier_JumpOrFall::ModifySpreadMultiplier_Implementation(float DeltaTime, float BaseSpreadMultiplier, float CurrentSpreadMultiplier, UARSSpreadPattern* SpreadPattern, UARSRecoilSpreadManager* RecoilSpreadManager)
{
if (!RecoilSpreadManager)
{
UE_LOG(LogARS, Error, TEXT("[%s] RecoilSpreadManager component is null! Returning the current spread multiplier."), *FString(__FUNCTION__));
return CurrentSpreadMultiplier;
}
if (!SpreadPattern)
{
UE_LOG(LogARS, Error, TEXT("[%s] SpreadPattern is null! Returning the current spread multiplier."), *FString(__FUNCTION__));
return CurrentSpreadMultiplier;
}
AController* Controller = RecoilSpreadManager->GetController();
APawn* Pawn = Controller ? Controller->GetPawn() : SpreadPattern->GetPawn();
if (!Pawn)
{
UE_LOG(LogARS, Verbose, TEXT("[%s] Pawn is null! Returning the current spread multiplier."), *FString(__FUNCTION__));
return CurrentSpreadMultiplier;
}
const UCharacterMovementComponent* CharMovementComp = Cast<UCharacterMovementComponent>(Pawn->GetMovementComponent());
// See if we are in the air (jumping/falling), and if so, smoothly apply the penalty
const bool bIsJumpingOrFalling = (CharMovementComp != nullptr) && CharMovementComp->IsFalling();
const float JumpFallTargetValue = bIsJumpingOrFalling ? SpreadAngleMultiplier_JumpingOrFalling : 1.0f;
// Smoothly interpolate to the target value
CurrentJumpFallMultiplier = FMath::FInterpTo(CurrentJumpFallMultiplier, JumpFallTargetValue, DeltaTime, TransitionRate_JumpingOrFalling);
// Apply the current jump/fall multiplier to the current spread multiplier
const float ModifiedSpreadMultiplier = CurrentSpreadMultiplier * CurrentJumpFallMultiplier;
// Check if the multiplier is at its minimum value
bIsAtMinMultiplier = FMath::IsNearlyEqual(CurrentJumpFallMultiplier, 1.0f, MultiplierNearlyEqualThreshold);
// Return the modified spread multiplier
return ModifiedSpreadMultiplier;
}
bool UARSSpreadModifier_JumpOrFall::IsAtMinMultiplier_Implementation() const
{
return bIsAtMinMultiplier;
}
Last updated