Lesson 8: Weapon Systems & Combat Mechanics

Welcome to Combat System Development!

Fantastic progress on map design! Now it's time to build the core combat experience that makes battle royale games exciting. In this lesson, you'll learn to design weapon systems, implement combat mechanics, create loot distribution, and optimize combat for smooth multiplayer gameplay. This is where your game becomes playable and fun.

Learning Objectives

By the end of this lesson, you will:

  • ✅ Design weapon systems with damage, range, and recoil mechanics
  • ✅ Implement weapon spawning and loot distribution systems
  • ✅ Create combat mechanics (shooting, reloading, aiming)
  • ✅ Build weapon attachments and customization systems
  • ✅ Implement hit detection and damage calculation
  • ✅ Optimize combat for multiplayer performance
  • ✅ Balance weapons for fair and engaging gameplay

Part 1: Weapon System Design

Step 1: Understanding Weapon Categories

1.1 Weapon Types

Battle royale games typically feature several weapon categories:

Assault Rifles:

  • Role: Versatile, medium-range combat
  • Characteristics: Balanced damage, moderate recoil, good range
  • Examples: AK-47, M4A1, SCAR-L
  • Best for: Most combat situations

Submachine Guns (SMGs):

  • Role: Close-range, high rate of fire
  • Characteristics: Lower damage, minimal recoil, short range
  • Examples: MP5, UMP45, P90
  • Best for: Indoor combat, close quarters

Sniper Rifles:

  • Role: Long-range precision
  • Characteristics: High damage, single-shot, long range
  • Examples: AWP, M24, Kar98k
  • Best for: Long-range engagements, eliminating targets

Shotguns:

  • Role: Extreme close-range power
  • Characteristics: Very high damage, spread pattern, very short range
  • Examples: Pump Shotgun, Double Barrel
  • Best for: Building clearing, close combat

Pistols:

  • Role: Backup weapon, early game
  • Characteristics: Low damage, fast fire rate, short range
  • Examples: Glock, Desert Eagle
  • Best for: Early game, secondary weapon

1.2 Weapon Rarity System

Implement weapon rarity to create loot progression:

  • Common (Gray): Basic weapons, lower stats
  • Uncommon (Green): Slightly better than common
  • Rare (Blue): Good weapons, balanced stats
  • Epic (Purple): Strong weapons, better attachments
  • Legendary (Gold): Best weapons, unique properties

Pro Tip: Use rarity to balance loot distribution. Common weapons should be frequent, legendary weapons should be rare.

Step 2: Weapon Data Structure

2.1 Weapon Base Class

Create a base weapon class in C++:

// Weapon.h
UCLASS(BlueprintType)
class BATTLEROYALE_API AWeaponBase : public AActor
{
    GENERATED_BODY()

public:
    // Weapon properties
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    FString WeaponName;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    EWeaponType WeaponType;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    float BaseDamage;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    float FireRate; // Shots per second

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    float EffectiveRange;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    int32 MagazineSize;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    float ReloadTime;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    EWeaponRarity Rarity;

    // Recoil pattern
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    TArray<FVector2D> RecoilPattern;

    // Current state
    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Weapon")
    int32 CurrentAmmo;

    UPROPERTY(Replicated, BlueprintReadOnly, Category = "Weapon")
    int32 ReserveAmmo;
};

2.2 Weapon Type Enum

// WeaponTypes.h
UENUM(BlueprintType)
enum class EWeaponType : uint8
{
    AssaultRifle,
    SMG,
    SniperRifle,
    Shotgun,
    Pistol,
    Melee
};

UENUM(BlueprintType)
enum class EWeaponRarity : uint8
{
    Common,
    Uncommon,
    Rare,
    Epic,
    Legendary
};

Part 2: Combat Mechanics Implementation

Step 3: Shooting System

3.1 Fire Weapon Function

Implement weapon firing with replication:

// Weapon.cpp
void AWeaponBase::FireWeapon()
{
    if (CurrentAmmo <= 0 || !CanFire())
    {
        return;
    }

    // Consume ammo
    CurrentAmmo--;

    // Server-side: Perform raycast for hit detection
    if (HasAuthority())
    {
        PerformHitScan();
    }

    // Client-side: Play effects
    PlayFireEffects();
    ApplyRecoil();

    // Schedule next shot based on fire rate
    GetWorldTimerManager().SetTimer(
        FireRateTimer,
        this,
        &AWeaponBase::FireWeapon,
        1.0f / FireRate,
        false
    );
}

void AWeaponBase::PerformHitScan()
{
    FVector StartLocation = GetActorLocation();
    FVector ForwardVector = GetActorForwardVector();
    FVector EndLocation = StartLocation + (ForwardVector * EffectiveRange);

    FHitResult HitResult;
    FCollisionQueryParams QueryParams;
    QueryParams.AddIgnoredActor(this);
    QueryParams.AddIgnoredActor(GetOwner());

    if (GetWorld()->LineTraceSingleByChannel(
        HitResult,
        StartLocation,
        EndLocation,
        ECC_Pawn,
        QueryParams
    ))
    {
        // Apply damage to hit actor
        if (AActor* HitActor = HitResult.GetActor())
        {
            ApplyDamage(HitActor, HitResult);
        }
    }
}

3.2 Damage Application

void AWeaponBase::ApplyDamage(AActor* Target, const FHitResult& HitResult)
{
    // Calculate damage based on distance
    float Distance = FVector::Dist(GetActorLocation(), HitResult.Location);
    float DamageMultiplier = CalculateDamageFalloff(Distance);
    float FinalDamage = BaseDamage * DamageMultiplier;

    // Apply damage to target
    if (APawn* TargetPawn = Cast<APawn>(Target))
    {
        // Use Unreal's damage system
        FDamageEvent DamageEvent;
        TargetPawn->TakeDamage(FinalDamage, DamageEvent, GetInstigatorController(), this);
    }
}

float AWeaponBase::CalculateDamageFalloff(float Distance)
{
    if (Distance <= EffectiveRange)
    {
        return 1.0f; // Full damage
    }

    // Linear falloff beyond effective range
    float FalloffRange = EffectiveRange * 2.0f;
    if (Distance >= FalloffRange)
    {
        return 0.0f; // No damage
    }

    float FalloffRatio = 1.0f - ((Distance - EffectiveRange) / EffectiveRange);
    return FMath::Clamp(FalloffRatio, 0.0f, 1.0f);
}

Step 4: Recoil System

4.1 Recoil Pattern Implementation

void AWeaponBase::ApplyRecoil()
{
    if (!RecoilPattern.IsValidIndex(CurrentRecoilIndex))
    {
        CurrentRecoilIndex = 0; // Reset pattern
    }

    FVector2D RecoilOffset = RecoilPattern[CurrentRecoilIndex];

    // Apply recoil to camera/aim
    if (APawn* OwnerPawn = Cast<APawn>(GetOwner()))
    {
        if (APlayerController* PC = Cast<APlayerController>(OwnerPawn->GetController()))
        {
            // Add pitch and yaw recoil
            PC->AddPitchInput(-RecoilOffset.Y * RecoilStrength);
            PC->AddYawInput(RecoilOffset.X * RecoilStrength);
        }
    }

    CurrentRecoilIndex++;
}

// Reset recoil when player stops firing
void AWeaponBase::ResetRecoil()
{
    CurrentRecoilIndex = 0;
}

4.2 Recoil Pattern Data

Create recoil patterns for different weapons:

// In weapon constructor or Blueprint
// Assault Rifle recoil pattern (up and to the right)
RecoilPattern.Add(FVector2D(0.0f, 0.5f));
RecoilPattern.Add(FVector2D(0.2f, 0.8f));
RecoilPattern.Add(FVector2D(0.3f, 1.0f));
RecoilPattern.Add(FVector2D(0.2f, 0.9f));
RecoilPattern.Add(FVector2D(0.1f, 0.7f));
// ... continue pattern

Part 3: Weapon Spawning and Loot Distribution

Step 5: Loot Spawn System

5.1 Loot Spawner Component

// LootSpawner.h
UCLASS(BlueprintType)
class BATTLEROYALE_API ULootSpawnerComponent : public UActorComponent
{
    GENERATED_BODY()

public:
    // Spawn loot at location
    UFUNCTION(BlueprintCallable)
    void SpawnLootAtLocation(FVector Location);

    // Spawn loot in area
    UFUNCTION(BlueprintCallable)
    void SpawnLootInArea(FVector Center, float Radius, int32 Count);

    // Loot table
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Loot")
    TArray<FLootTableEntry> LootTable;
};

// LootTableEntry.h
USTRUCT(BlueprintType)
struct FLootTableEntry
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSubclassOf<AWeaponBase> WeaponClass;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float SpawnWeight; // Higher = more likely to spawn

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    EWeaponRarity MinRarity;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    EWeaponRarity MaxRarity;
};

5.2 Weighted Random Selection

TSubclassOf<AWeaponBase> ULootSpawnerComponent::SelectRandomWeapon()
{
    float TotalWeight = 0.0f;
    for (const FLootTableEntry& Entry : LootTable)
    {
        TotalWeight += Entry.SpawnWeight;
    }

    float RandomValue = FMath::RandRange(0.0f, TotalWeight);
    float CurrentWeight = 0.0f;

    for (const FLootTableEntry& Entry : LootTable)
    {
        CurrentWeight += Entry.SpawnWeight;
        if (RandomValue <= CurrentWeight)
        {
            return Entry.WeaponClass;
        }
    }

    return nullptr;
}

Step 6: Loot Distribution Strategy

6.1 Balanced Loot Distribution

Design loot distribution for balanced gameplay:

High-Value Locations (5-10% of map):

  • Military bases, airports, major cities
  • Higher chance of rare/epic/legendary weapons
  • More loot spawns per location

Medium-Value Locations (20-30% of map):

  • Towns, compounds, smaller POIs
  • Mix of common/rare weapons
  • Moderate loot spawns

Low-Value Locations (60-70% of map):

  • Small buildings, camps, scattered locations
  • Mostly common/uncommon weapons
  • Fewer loot spawns

6.2 Loot Spawn Density

void ULootSpawnerComponent::SpawnLootInArea(FVector Center, float Radius, int32 Count)
{
    for (int32 i = 0; i < Count; i++)
    {
        // Random position within radius
        float Angle = FMath::RandRange(0.0f, 2.0f * PI);
        float Distance = FMath::RandRange(0.0f, Radius);
        FVector SpawnLocation = Center + FVector(
            FMath::Cos(Angle) * Distance,
            FMath::Sin(Angle) * Distance,
            0.0f
        );

        // Check for valid spawn location (not inside geometry)
        FHitResult HitResult;
        if (GetWorld()->LineTraceSingleByChannel(
            HitResult,
            SpawnLocation + FVector(0, 0, 100),
            SpawnLocation - FVector(0, 0, 100),
            ECC_WorldStatic
        ))
        {
            SpawnLocation = HitResult.Location + FVector(0, 0, 50); // Slightly above ground
            SpawnLootAtLocation(SpawnLocation);
        }
    }
}

Part 4: Weapon Attachments and Customization

Step 7: Attachment System

7.1 Attachment Types

Design attachment categories:

Sights/Scopes:

  • Red Dot Sight (close range)
  • Holographic Sight (close-medium range)
  • 2x Scope (medium range)
  • 4x Scope (medium-long range)
  • 8x Scope (long range)

Barrel Attachments:

  • Suppressor (reduces sound, slightly reduces damage)
  • Compensator (reduces horizontal recoil)
  • Muzzle Brake (reduces vertical recoil)

Magazine Attachments:

  • Extended Magazine (increases ammo capacity)
  • Quick Reload Magazine (faster reload speed)

Grip Attachments:

  • Vertical Grip (reduces recoil)
  • Angled Grip (faster ADS time)

7.2 Attachment Implementation

// WeaponAttachment.h
USTRUCT(BlueprintType)
struct FWeaponAttachment
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    EAttachmentType Type;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString AttachmentName;

    // Stat modifiers
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float DamageModifier;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float RecoilReduction;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float RangeModifier;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float ReloadSpeedModifier;
};

// In WeaponBase class
UPROPERTY(Replicated, BlueprintReadOnly, Category = "Weapon")
TArray<FWeaponAttachment> AttachedMods;

void AWeaponBase::ApplyAttachment(const FWeaponAttachment& Attachment)
{
    AttachedMods.Add(Attachment);

    // Apply modifiers
    BaseDamage *= (1.0f + Attachment.DamageModifier);
    RecoilStrength *= (1.0f - Attachment.RecoilReduction);
    EffectiveRange *= (1.0f + Attachment.RangeModifier);
    ReloadTime *= (1.0f - Attachment.ReloadSpeedModifier);
}

Part 5: Reloading and Ammo Management

Step 8: Reload System

8.1 Reload Implementation

void AWeaponBase::StartReload()
{
    if (CurrentAmmo >= MagazineSize || ReserveAmmo <= 0)
    {
        return; // Already full or no ammo
    }

    // Play reload animation
    PlayReloadAnimation();

    // Set reload timer
    GetWorldTimerManager().SetTimer(
        ReloadTimer,
        this,
        &AWeaponBase::CompleteReload,
        ReloadTime,
        false
    );
}

void AWeaponBase::CompleteReload()
{
    int32 AmmoNeeded = MagazineSize - CurrentAmmo;
    int32 AmmoToReload = FMath::Min(AmmoNeeded, ReserveAmmo);

    CurrentAmmo += AmmoToReload;
    ReserveAmmo -= AmmoToReload;

    GetWorldTimerManager().ClearTimer(ReloadTimer);
}

void AWeaponBase::CancelReload()
{
    GetWorldTimerManager().ClearTimer(ReloadTimer);
    // Cancel reload animation
}

Part 6: Combat Optimization

Step 9: Network Optimization

9.1 Replication Optimization

Optimize weapon replication for multiplayer:

// In WeaponBase.h
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    // Only replicate to owner and nearby players
    DOREPLIFETIME_CONDITION(AWeaponBase, CurrentAmmo, COND_OwnerOnly);
    DOREPLIFETIME_CONDITION(AWeaponBase, ReserveAmmo, COND_OwnerOnly);

    // Replicate weapon state to all clients
    DOREPLIFETIME(AWeaponBase, WeaponType);
    DOREPLIFETIME(AWeaponBase, Rarity);
    DOREPLIFETIME(AWeaponBase, AttachedMods);
}

9.2 Hit Detection Optimization

Use server-authoritative hit detection:

// Server-side validation
bool AWeaponBase::ValidateHit(AActor* Target, FVector HitLocation)
{
    // Check if target is within reasonable range
    float Distance = FVector::Dist(GetActorLocation(), HitLocation);
    if (Distance > EffectiveRange * 1.5f)
    {
        return false; // Too far, likely cheating
    }

    // Check line of sight
    FHitResult HitResult;
    if (GetWorld()->LineTraceSingleByChannel(
        HitResult,
        GetActorLocation(),
        HitLocation,
        ECC_Visibility
    ))
    {
        return HitResult.GetActor() == Target;
    }

    return false;
}

Step 10: Performance Optimization

10.1 Object Pooling for Projectiles

For projectile-based weapons, use object pooling:

// ProjectilePool.h
class BATTLEROYALE_API AProjectilePool : public AActor
{
    GENERATED_BODY()

public:
    AProjectile* GetPooledProjectile();
    void ReturnProjectile(AProjectile* Projectile);

private:
    UPROPERTY()
    TArray<AProjectile*> AvailableProjectiles;

    UPROPERTY()
    TArray<AProjectile*> ActiveProjectiles;
};

10.2 Effect Optimization

Optimize visual and audio effects:

  • Use particle pooling for muzzle flashes
  • Limit simultaneous sounds (max 4-8 weapon sounds at once)
  • Use LODs for weapon models
  • Disable effects beyond certain distance

Troubleshooting Common Issues

Issue: Weapons Not Firing in Multiplayer

Solution:

  • Ensure weapon firing is called on server (use RPC)
  • Check replication settings for weapon class
  • Verify owner is set correctly
  • Check network role (should be Authority on server)

Issue: Hit Detection Not Working

Solution:

  • Verify line trace is performed on server
  • Check collision channels are set correctly
  • Ensure hit targets have collision enabled
  • Validate hit detection with server-side checks

Issue: Recoil Feels Inconsistent

Solution:

  • Normalize recoil pattern values
  • Apply recoil smoothing over time
  • Consider player's input sensitivity
  • Test with different frame rates

Issue: Loot Not Spawning

Solution:

  • Check loot spawner is called on server
  • Verify spawn locations are valid (not inside geometry)
  • Check loot table is populated
  • Ensure spawn permissions are set correctly

Pro Tips for Combat Design

  1. Start Simple: Begin with basic shooting, then add complexity
  2. Test Extensively: Playtest weapons with real players
  3. Balance Continuously: Adjust damage, range, and recoil based on feedback
  4. Use Data: Track weapon usage and win rates to identify imbalances
  5. Consider TTK: Time-to-kill should feel fair (not too fast, not too slow)
  6. Visual Feedback: Clear hit indicators, damage numbers, and impact effects
  7. Audio Design: Distinct weapon sounds help players identify threats
  8. Network First: Design for multiplayer from the start, not as an afterthought

Summary

Congratulations! You've learned to build comprehensive weapon and combat systems for your battle royale game. You can now:

  • ✅ Design weapon systems with proper stats and balance
  • ✅ Implement shooting mechanics with hit detection
  • ✅ Create loot spawning and distribution systems
  • ✅ Build weapon attachments and customization
  • ✅ Optimize combat for multiplayer performance

What's Next?

In Lesson 9: Matchmaking & Lobby Systems, you'll learn to:

  • Design matchmaking algorithms for balanced matches
  • Create lobby systems for pre-game preparation
  • Implement player queuing and team formation
  • Build server selection and region management
  • Handle player disconnections and reconnections

Related Resources

Practice Exercise

Your Mission: Create a complete weapon system with:

  • At least 3 different weapon types (assault rifle, SMG, sniper)
  • Loot spawning system with rarity distribution
  • Basic attachment system (at least 2 attachment types)
  • Reload mechanics
  • Hit detection and damage application

Test your weapons in multiplayer and gather feedback on balance and feel.


Ready to build epic combat systems? Bookmark this lesson and start implementing weapons in your battle royale game. The combat mechanics you create here will define the core gameplay experience that keeps players engaged.