Lyra Integration

Advanced Weapon Mechanics Integration Guide

📘 Overview

This guide explains how to integrate the Adaptive Recoil & Spread system into Lyra Starter Game's weapon framework. The plugin replaces Lyra's native spread calculations while adding new recoil and spread mechanics through component-based architecture.

⚠️ Requirements

How to install Lyra Starter Game

🛠️ Before You Begin

  1. Complete Plugin Installation: Ensure you've successfully installed the plugin following our Installation Guide.

  2. Backup Your Project: Recommended for first-time integrations (File → Save All with Derived Data)

🧩 Integration Workflow

🔌 Section 1: Plugin Setup for Lyra

Follow our comprehensive Installation Guide to:

  1. Install the plugin in Lyra's /Plugins/ directory

  2. Regenerate project files

  • Right-click → LyraStarterGame.uplugin

  • Select Generate Visual Studio Project Files

  1. Configure build dependencies:

  • Open LyraGame.build.cs under the Sources/LyraGame folder

  • Add "AdaptiveRecoilSpread" module to PrivateDependencyModuleNames array.

PrivateDependencyModuleNames.AddRange(
				new string[] {
				"InputCore",
				"Slate",
				"SlateCore",
				"RenderCore",
				"DeveloperSettings",
				"EnhancedInput",
				"NetCore",
				"RHI",
				"Projects",
				"Gauntlet",
				"UMG",
				"CommonUI",
				"CommonInput",
				"GameSettings",
				"CommonGame",
				"CommonUser",
				"GameSubtitles",
				"GameplayMessageRuntime",
				"AudioMixer",
				"NetworkReplayStreaming",
				"UIExtension",
				"ClientPilot",
				"AudioModulation",
				"EngineSettings",
				"DTLSHandlerComponent",
				"Json",
				"AdaptiveRecoilSpread" // Add ARS plugin to modules
			}
);
  1. Compile the Lyra project with settings

  • Set "LyraGame" as your startup project

  • Solution Configuration: "Development Editor"

  • Solution Platforms: "Win64" (Depends on your platforms)

  1. Verify successful compilation

ARS plugin setup via Epic Games Launcher
Manuel ARS plugin setup (Optional)
Build Lyra with ARS plugin

🎯 Section 2: Implementing Recoil and Spread Management

The ARSRecoilSpreadManager component handles real-time recoil/spread calculations and network synchronization. In Lyra, this must be added to controllers rather than pawns/characters to ensure proper multiplayer replication. Follow the implementation steps below:

Blueprint Configuration (Data Asset)

  1. Open ShooterCore Data Asset

    • Navigate to:

      /All/Plugins/ShooterCore Content/ → ShooterCore

Note: You must enable 'Show Plugin Content' to see ShooterCore Content in Favorites.

Enable Show Plugin Content
  1. Locate Component Setup

  • Go to: Actions → Add Components → Component List

  1. Add New Component Entry

  • Click + Add in the Component List array

  • Configure as:

    Actor Class: Controller  
    Component Class: ARSRecoilSpreadManager  
    Client Component: True  
    Server Component: True  

Critical Configuration Details

Property
Value
Why It Matters

Actor Class

Controller

Ensures component exists for each player

Client/Server

Both True

Enables prediction and authority reconciliation

Spawn Priority

500+

Ensures creation before weapon components

How to add ARSRecoilSpreadManager to Controller

⚙️ Section 3: Weapon Equipment Instance Modification

Overview

Lyra uses ULyraRangedWeaponInstance to represents weapons and define their behaviors. This section modifies this class to:

  1. Store Recoil and Spread pattern assets

  2. Sync patterns with ARSRecoilSpreadManager

  3. Replace Lyra's native spread calculations

Implementation Steps

1. Header File Modifications (ULyraRangedWeaponInstance.h)

// Source/LyraGame/Weapons/LyraRangedWeaponInstance.h

#pragma once

#include "LyraWeaponInstance.h"
#include "AbilitySystem/LyraAbilitySourceInterface.h"
#include "Curves/CurveFloat.h"

#include "LyraRangedWeaponInstance.generated.h"

class UARSRecoilSpreadManager;
class UARSSpreadPattern;
class UARSRecoilPattern;
class UPhysicalMaterial;

/**
 * ULyraRangedWeaponInstance
 *
 * A piece of equipment representing a ranged weapon spawned and applied to a pawn
 */
UCLASS()
class LYRAGAME_API ULyraRangedWeaponInstance : public ULyraWeaponInstance, public ILyraAbilitySourceInterface
{
	GENERATED_BODY()

public:
	ULyraRangedWeaponInstance(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

	virtual void PostInitProperties() override;

	//~ULyraEquipmentInstance interface
	virtual void OnEquipped() override;
	virtual void OnUnequipped() override;
	//~End of ULyraEquipmentInstance interface

	//~ILyraAbilitySourceInterface interface
	virtual float GetDistanceAttenuation(float Distance, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr) const override;
	virtual float GetPhysicalMaterialAttenuation(const UPhysicalMaterial* PhysicalMaterial, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr) const override;
	//~End of ILyraAbilitySourceInterface interface

public:
	int32 GetBulletsPerCartridge() const;

	float GetMaxDamageRange() const;

	float GetBulletTraceSweepRadius() const;

	// Add recoil and spread for next shot
	virtual void ApplyRecoilAndSpread();

	/** Returns the current spread angle (in degrees, diametrical) */
	float GetCalculatedSpreadAngle() const;

	float GetCalculatedSpreadAngleMultiplier() const;

	float GetSpreadExponent() const;

	bool HasFirstShotAccuracy() const;

	UARSRecoilSpreadManager* GetRecoilSpreadManager() const;

protected:
	// Number of bullets to fire in a single cartridge (typically 1, but may be more for shotguns)
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon Config", meta = (ClampMin = "1"))
	int32 BulletsPerCartridge = 1;

	// The maximum distance at which this weapon can deal damage
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon Config", meta=(ForceUnits=cm))
	float MaxDamageRange = 25000.0f;

	// The radius for bullet traces sweep spheres (0.0 will result in a line trace)
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon Config", meta=(ForceUnits=cm), meta = (ClampMin = "0.0"))
	float BulletTraceSweepRadius = 0.0f;

	/**
	 * A curve that maps the distance (in cm) to a multiplier on the base damage from the associated gameplay effect.
	 * If there is no data in this curve, then the weapon is assumed to have no falloff with distance.
	 */
	UPROPERTY(EditAnywhere, Category = "Weapon")
	FRuntimeFloatCurve DistanceDamageFalloff;

	/**
	 * List of special tags that affect how damage is dealt.
	 * These tags will be compared to tags in the physical material of the thing being hit.
	 * If more than one tag is present, the multipliers will be combined multiplicatively.
	 */
	UPROPERTY(EditAnywhere, Category = "Weapon")
	TMap<FGameplayTag, float> MaterialDamageMultiplier;

	// Enable/disable recoil mechanics for weapon
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon|Recoil")
	bool bEnableRecoil = true;

	/*
	 * Recoil pattern for shooting
	 * For change or get pattern in runtime use GetRecoilPattern/SetRecoilPattern function
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Weapon|Recoil")
	TObjectPtr<UARSRecoilPattern> RecoilPattern;

	// Enable/disable spread mechanics for weapon
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon|Spread")
	bool bEnableSpread = true;

	/*
	 * Spread pattern for shooting
	 * For change or get pattern in runtime use GetSpreadPattern/SetSpreadPattern function
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Weapon|Spread")
	TObjectPtr<UARSSpreadPattern> SpreadPattern;

	/**
	 * Cached reference to the RecoilSpreadManager component.
	 */
	UPROPERTY()
	mutable TWeakObjectPtr<UARSRecoilSpreadManager> CachedRecoilSpreadManager;
};

2. Source File Updates (LyraRangedWeaponInstance.cpp)

// Source/LyraGame/Weapons/LyraRangedWeaponInstance.cpp

#include "LyraRangedWeaponInstance.h"

#include "AbilitySystemComponent.h"
#include "LyraLogChannels.h"
#include "NativeGameplayTags.h"
#include "Components/ARSRecoilSpreadManager.h"
#include "GameFramework/Pawn.h"
#include "Physics/PhysicalMaterialWithTags.h"
#include "Weapons/LyraWeaponInstance.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraRangedWeaponInstance)

UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Lyra_Weapon_SteadyAimingCamera, "Lyra.Weapon.SteadyAimingCamera");

ULyraRangedWeaponInstance::ULyraRangedWeaponInstance(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{

}

void ULyraRangedWeaponInstance::PostInitProperties()
{
	Super::PostInitProperties();
}

void ULyraRangedWeaponInstance::OnEquipped()
{
	Super::OnEquipped();

	if (UARSRecoilSpreadManager* RecoilSpreadManager = GetRecoilSpreadManager())
	{
		RecoilSpreadManager->SetSpreadPattern(SpreadPattern);
		RecoilSpreadManager->SetRecoilPattern(RecoilPattern);
	}
}

void ULyraRangedWeaponInstance::OnUnequipped()
{
	if (UARSRecoilSpreadManager* RecoilSpreadManager = GetRecoilSpreadManager())
	{
		RecoilSpreadManager->SetSpreadPattern(nullptr);
		RecoilSpreadManager->SetRecoilPattern(nullptr);
	}
	
	Super::OnUnequipped();
}

float ULyraRangedWeaponInstance::GetDistanceAttenuation(float Distance, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags) const
{
	const FRichCurve* Curve = DistanceDamageFalloff.GetRichCurveConst();
	return Curve->HasAnyData() ? Curve->Eval(Distance) : 1.0f;
}

float ULyraRangedWeaponInstance::GetPhysicalMaterialAttenuation(const UPhysicalMaterial* PhysicalMaterial, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags) const
{
	float CombinedMultiplier = 1.0f;
	if (const UPhysicalMaterialWithTags* PhysMatWithTags = Cast<const UPhysicalMaterialWithTags>(PhysicalMaterial))
	{
		for (const FGameplayTag MaterialTag : PhysMatWithTags->Tags)
		{
			if (const float* pTagMultiplier = MaterialDamageMultiplier.Find(MaterialTag))
			{
				CombinedMultiplier *= *pTagMultiplier;
			}
		}
	}

	return CombinedMultiplier;
}

int32 ULyraRangedWeaponInstance::GetBulletsPerCartridge() const
{
	return BulletsPerCartridge;
}

float ULyraRangedWeaponInstance::GetMaxDamageRange() const
{
	return MaxDamageRange;
}

float ULyraRangedWeaponInstance::GetBulletTraceSweepRadius() const
{
	return BulletTraceSweepRadius;
}

void ULyraRangedWeaponInstance::ApplyRecoilAndSpread()
{
	const APawn* Pawn = GetPawn();
	const AController* Controller = Pawn->GetController();

	if (UARSRecoilSpreadManager* RecoilSpreadManager = GetRecoilSpreadManager())
	{
		RecoilSpreadManager->Fire();
	}
	else
	{
		UE_LOG(LogLyraWeaponInstance, Error, TEXT("RecoilSpreadManager could not be found!"));
	}
}

float ULyraRangedWeaponInstance::GetCalculatedSpreadAngle() const
{
	if (UARSRecoilSpreadManager* RecoilSpreadManager = GetRecoilSpreadManager())
	{
		return RecoilSpreadManager->GetCurrentSpreadAngle();
	}
	return 0.0f;
}

float ULyraRangedWeaponInstance::GetCalculatedSpreadAngleMultiplier() const
{
	if (UARSRecoilSpreadManager* RecoilSpreadManager = GetRecoilSpreadManager())
	{
		return RecoilSpreadManager->GetCalculatedSpreadAngleMultiplier();
	}
	return 1.0f;
}

float ULyraRangedWeaponInstance::GetSpreadExponent() const
{
	if (UARSRecoilSpreadManager* RecoilSpreadManager = GetRecoilSpreadManager())
	{
		return RecoilSpreadManager->GetSpreadExponent();
	}
	return 1.0f;
}

bool ULyraRangedWeaponInstance::HasFirstShotAccuracy() const
{
	if (UARSRecoilSpreadManager* RecoilSpreadManager = GetRecoilSpreadManager())
	{
		return RecoilSpreadManager->HasFirstShotAccuracy();
	}
	return false;
}

UARSRecoilSpreadManager* ULyraRangedWeaponInstance::GetRecoilSpreadManager() const
{
	if (!CachedRecoilSpreadManager.IsValid())
    {
        if (const APawn* Pawn = GetPawn())
        {
            if (const AController* Controller = Pawn->GetController())
            {
                CachedRecoilSpreadManager = Controller->FindComponentByClass<UARSRecoilSpreadManager>();
            }
        }
    }
    
    return CachedRecoilSpreadManager.Get();
}
Lyra weapon instance modification

📈 Section 4: Optimizing Weapon State Management

Disabling Legacy Spread Updates in Lyra

Why This Matters

The ULyraWeaponStateComponent originally handled spread calculations through continuous ticking. Our Adaptive Recoil & Spread System makes this obsolete. This modification: ✅ Improves Performance: Eliminates unnecessary tick overhead ✅ Prevents Conflicts: Ensures single source of truth for weapon mechanics ✅ Maintains Compatibility: Preserves non-spread related functionality

Implementation Steps

1. Header File Modifications (LyraWeaponStateComponent.h)

// Source/LyraGame/Weapons/LyraWeaponStateComponent.h

// Tracks weapon state and recent confirmed hit markers to display on screen
UCLASS()
class ULyraWeaponStateComponent : public UControllerComponent
{
	GENERATED_BODY()

public:

	ULyraWeaponStateComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

	// Comment or remove TickComponent function declaration
	// virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
	
};

2. Source File Updates (LyraWeaponStateComponent.cpp)

// Source/LyraGame/Weapons/LyraWeaponStateComponent.cpp

ULyraWeaponStateComponent::ULyraWeaponStateComponent(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SetIsReplicatedByDefault(true);

	// Disable ticking for better performance
	PrimaryComponentTick.bStartWithTickEnabled = false;
	PrimaryComponentTick.bCanEverTick = true;
}

// Remove or comment entire TickComponent function
// void ULyraWeaponStateComponent::TickComponent(...) { ... }
Optimizing Lyra weapon system

🔥 Section 5: Modernizing Weapon Fire Mechanics

Integrating Adaptive Recoil & Spread with Lyra's Gameplay Abilities

Architectural Overview

The ULyraGameplayAbility_RangedWeapon ability drives weapon firing logic. We modify ULyraGameplayAbility_RangedWeapon ability to synchronize with our recoil/spread system through three critical phases:

  1. Initialize recoil patterns on ability start (ActivateAbility)

  2. Apply per-shot recoil/spread calculations (OnTargetDataReadyCallback)

  3. Handle post-fire recovery (EndAbility)

Source File Updates (LyraGameplayAbility_RangedWeapon.cpp)

// Source/LyraGame/Weapons/LyraGameplayAbility_RangedWeapon.cpp

#include "LyraGameplayAbility_RangedWeapon.h"
#include "AbilitySystemComponent.h"
#include "AIController.h"
#include "DrawDebugHelpers.h"
#include "LyraLogChannels.h"
#include "LyraRangedWeaponInstance.h"
#include "NativeGameplayTags.h"
#include "AbilitySystem/LyraGameplayAbilityTargetData_SingleTargetHit.h"
#include "Components/ARSRecoilSpreadManager.h" // Add RecoilSpreadManager component
#include "Physics/LyraCollisionChannels.h"
#include "Weapons/LyraWeaponStateComponent.h"

void ULyraGameplayAbility_RangedWeapon::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	// Bind target data callback
	UAbilitySystemComponent* MyAbilityComponent = CurrentActorInfo->AbilitySystemComponent.Get();
	check(MyAbilityComponent);

	OnTargetDataReadyCallbackDelegateHandle = MyAbilityComponent->AbilityTargetDataSetDelegate(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey()).AddUObject(this, &ThisClass::OnTargetDataReadyCallback);

	// Update the last firing time
	ULyraRangedWeaponInstance* WeaponData = GetWeaponInstance();
	check(WeaponData);
	WeaponData->UpdateFiringTime();

	// Begin firing sequence for recoil
	AController* Controller = GetControllerFromActorInfo();
	UARSRecoilSpreadManager* RecoilSpreadManager = Controller ? Controller->FindComponentByClass<UARSRecoilSpreadManager>() : nullptr;
	if (RecoilSpreadManager)
	{
		RecoilSpreadManager->BeginFiring();
	}

	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
}

void ULyraGameplayAbility_RangedWeapon::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
	if (IsEndAbilityValid(Handle, ActorInfo))
	{
		if (ScopeLockCount > 0)
		{
			WaitingToExecute.Add(FPostLockDelegate::CreateUObject(this, &ThisClass::EndAbility, Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled));
			return;
		}

		UAbilitySystemComponent* MyAbilityComponent = CurrentActorInfo->AbilitySystemComponent.Get();
		check(MyAbilityComponent);

		// When ability ends, consume target data and remove delegate
		MyAbilityComponent->AbilityTargetDataSetDelegate(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey()).Remove(OnTargetDataReadyCallbackDelegateHandle);
		MyAbilityComponent->ConsumeClientReplicatedTargetData(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey());

		// End firing sequence for recoil
		AController* Controller = GetControllerFromActorInfo();
		UARSRecoilSpreadManager* RecoilSpreadManager = Controller ? Controller->FindComponentByClass<UARSRecoilSpreadManager>() : nullptr;
		if (RecoilSpreadManager)
		{
			RecoilSpreadManager->EndFiring();
		}

		Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
	}
}

void ULyraGameplayAbility_RangedWeapon::OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& InData, FGameplayTag ApplicationTag)
{
	UAbilitySystemComponent* MyAbilityComponent = CurrentActorInfo->AbilitySystemComponent.Get();
	check(MyAbilityComponent);

	if (const FGameplayAbilitySpec* AbilitySpec = MyAbilityComponent->FindAbilitySpecFromHandle(CurrentSpecHandle))
	{
		FScopedPredictionWindow	ScopedPrediction(MyAbilityComponent);

		// Take ownership of the target data to make sure no callbacks into game code invalidate it out from under us
		FGameplayAbilityTargetDataHandle LocalTargetDataHandle(MoveTemp(const_cast<FGameplayAbilityTargetDataHandle&>(InData)));

		const bool bShouldNotifyServer = CurrentActorInfo->IsLocallyControlled() && !CurrentActorInfo->IsNetAuthority();
		if (bShouldNotifyServer)
		{
			MyAbilityComponent->CallServerSetReplicatedTargetData(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey(), LocalTargetDataHandle, ApplicationTag, MyAbilityComponent->ScopedPredictionKey);
		}

		const bool bIsTargetDataValid = true;

		bool bProjectileWeapon = false;

#if WITH_SERVER_CODE
		if (!bProjectileWeapon)
		{
			if (AController* Controller = GetControllerFromActorInfo())
			{
				if (Controller->GetLocalRole() == ROLE_Authority)
				{
					// Confirm hit markers
					if (ULyraWeaponStateComponent* WeaponStateComponent = Controller->FindComponentByClass<ULyraWeaponStateComponent>())
					{
						TArray<uint8> HitReplaces;
						for (uint8 i = 0; (i < LocalTargetDataHandle.Num()) && (i < 255); ++i)
						{
							if (FGameplayAbilityTargetData_SingleTargetHit* SingleTargetHit = static_cast<FGameplayAbilityTargetData_SingleTargetHit*>(LocalTargetDataHandle.Get(i)))
							{
								if (SingleTargetHit->bHitReplaced)
								{
									HitReplaces.Add(i);
								}
							}
						}

						WeaponStateComponent->ClientConfirmTargetData(LocalTargetDataHandle.UniqueId, bIsTargetDataValid, HitReplaces);
					}

				}
			}
		}
#endif //WITH_SERVER_CODE


		// See if we still have ammo
		if (bIsTargetDataValid && CommitAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo))
		{
			// We fired the weapon, add recoil/spread
			ULyraRangedWeaponInstance* WeaponInstance = GetWeaponInstance();
			check(WeaponInstance);
			
			// Apply Recoil and Spread to weapon
			WeaponInstance->ApplyRecoilAndSpread();

			// Let the blueprint do stuff like apply effects to the targets
			OnRangedWeaponTargetDataReady(LocalTargetDataHandle);
		}
		else
		{
			UE_LOG(LogLyraAbilitySystem, Warning, TEXT("Weapon ability %s failed to commit (bIsTargetDataValid=%d)"), *GetPathName(), bIsTargetDataValid ? 1 : 0);
			K2_EndAbility();
		}
	}

	// We've processed the data
	MyAbilityComponent->ConsumeClientReplicatedTargetData(CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey());
}
Updating Lyra's weapon fire mechanics (GAS)

✅ Section 6: Final Compilation & Validation

Completing the Integration Process

Build Instructions

Follow this sequence to ensure error-free compilation:

  1. Clean Existing Build

    # Visual Studio
    Build → Clean Solution
    
    # Manual Cleanup
    Delete /Binaries/ and /Intermediate/ folders
  2. Regenerate Project Files Right-click .uprojectGenerate Visual Studio Project Files

  3. Full Rebuild

    Build → Build Solution (Ctrl+Shift+B)
  4. Verify in Editor

    • Open Lyra project

    • Check for warnings in Message Log (Window → Developer Tools → Message Log)

Final build for Lyra and start using Adaptive Recoil&Spread plugin

🧠 Section 7: Configuring Weapon Patterns

Creating & Assigning Recoil/Spread Behaviors

Now you can create Recoil/Spread patterns for weapons and add them to corresponding weapon equipment instances. Check out Recoil and Spread patterns to learn pattern creation workflow.

Add Recoil and Spread Patterns To Weapons

Pattern Design Best Practices

Example Configurations

Weapon Type
Recoil Pattern
Spread Pattern

Assault Rifle

Fast vertical climb Moderate horizontal sway

0.5° base +0.2°/shot

Shotgun

Single sharp kick Slow recovery

5° base +1.5°/shot

Sniper Rifle

Minimal vertical Random horizontal

0.1° base No burst penalty

🎉 Integration Complete!

Your Lyra project is now equipped with AAA-grade recoil and spread mechanics, powered by Adaptive Recoil & Spread. You can now explore, customize, and scale your weapon systems with full confidence.

Need help or want to go further?

Join our Discord Community for support, feedback, and discussions.

🧪 Test the Full Experience

To experience the full potential of ARS in a real-world production context, we’ve prepared a dedicated demo project:

🎮 ARS_Lyra_Demo

A fully playable, ready-to-build Lyra-based example showcasing advanced recoil & spread behaviors, and debugging tools. → Download ARS_Lyra_Demo

💻 ARS_Lyra_Project (Source Code)

The complete source code for the demo project. Study or extend the implementation in your own workflow. → Download and view ARS_Lyra_Project

Last updated