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

  1. In the Content Browser, right-click and select Recoil & Spread → Spread Pattern

  2. Name your pattern (e.g., "B_Spread_Rifle")

  3. Double-click to open the pattern in the custom editor


Spread Pattern 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.

Parameter
Description

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

  1. Initial State: Heat=0 → Spread=2°

  2. Fire 3 Shots:

    • Heat increases via HeatPerShotCurve → Heat=25 → Spread=8°

  3. Stop Firing:

    • After 0.5s delay, heat decays via CoolDownCurve


3. Spread Calculation

Final spread angle combines multiple factors:

EffectiveSpread = HeatToSpreadCurve(CurrentHeat) * SpreadMultiplier  

Components

Factor
Source

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:

Class
Condition
Behavior

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 Variable
Purpose

Debug_Min/MaxHeat

Heat range boundaries

Debug_CurrentHeat

Real-time heat value

Debug_CurrentSpread

Live spread angle


7. Best Practices

  1. Curve Design:

    • Keep Heat→SpreadCurve X-range matching HeatPerShot accumulation

  2. Modifier Stacking:

    • Order modifiers by priority (e.g., movement before stance)

  3. 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:

  1. Extending the UARSSpreadModifier class

  2. Implementing the required modification methods

  3. 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)

#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)

#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