Gun Centric Refactor Start

This commit is contained in:
2025-07-04 03:26:03 -07:00
parent 1f0381506a
commit e7989cfe0a
30 changed files with 3783 additions and 96 deletions
+291
View File
@@ -0,0 +1,291 @@
# Gun-Centric Architecture Guide
The EasyBallistics plugin has been refactored to use a gun-centric approach where weapons are the primary actors with modular components. This consolidates scattered settings and provides a more intuitive workflow for creating realistic weapons.
## Overview
The new architecture consists of:
1. **AEBGun** - Main weapon actor
2. **UEBMagazine** - Magazine component for ammunition storage
3. **UEBBarrel** - Refocused barrel component for ballistics
4. **UEBWeaponConfiguration** - Asset that consolidates all weapon settings
5. **UEBBulletPropertiesAsset** - Bullet characteristics (unchanged)
## Core Components
### Gun Actor (AEBGun)
The main weapon actor that brings everything together:
```cpp
// C++ Usage
AEBGun* MyGun = GetWorld()->SpawnActor<AEBGun>();
MyGun->ApplyWeaponConfiguration(MyM4Config);
MyGun->LoadMagazine(BulletArray);
MyGun->PullTrigger();
```
**Key Features:**
- Centralized weapon state management
- Realistic gun controls (safety, charging handle, trigger)
- Automatic configuration from weapon assets
- Full multiplayer support
- Event-driven architecture
### Magazine Component (UEBMagazine)
Handles ammunition storage and feeding:
**Magazine Types:**
- Standard Box Magazine
- Drum Magazine
- Tube Magazine (shotguns)
- Stripper Clip
- Belt Fed (machine guns)
- Revolver Cylinder
- Internal Magazine (bolt-actions)
**Features:**
- Mixed ammunition support
- Malfunction simulation
- Bolt hold-open functionality
- Realistic capacity limits
- Feed rate control for belt-fed weapons
### Barrel Component (UEBBarrel)
Fully integrated with the gun system for comprehensive ballistics:
**Enhanced Integration:**
- **Parent Gun Reference**: Direct connection to main gun actor
- **Configuration Driven**: All settings applied from weapon configuration
- **Realistic Calculations**: Physics-based muzzle velocity and accuracy
- **Legacy Compatibility**: Maintains old ammo system for existing projects
**Barrel Properties Applied from Config:**
- Barrel length, rifling twist rate, bore radius
- Muzzle velocity range and variations
- Accuracy and spread characteristics
- Recoil direction and multipliers
**Smart Calculations:**
```cpp
// Barrel automatically calculates effective properties
float MuzzleVel = Gun->GetEffectiveMuzzleVelocity(); // Considers bullet weight, barrel length
float Accuracy = Gun->GetBarrelAccuracy(); // Factors in barrel length, rifling
FVector Recoil = Gun->CalculateRecoilImpulse(); // Physics-based recoil calculation
```
**Features:**
- Automatic muzzle velocity calculation based on barrel length and bullet weight
- Rifling twist optimization for different bullet types
- Physics-based recoil impulse generation
- Direct magazine and gun state integration
### Weapon Configuration Asset (UEBWeaponConfiguration)
Consolidates all weapon settings into a single, manageable asset:
**Configuration Sections:**
- **Barrel Configuration**: Length, rifling, muzzle velocity, accuracy
- **Fire Control**: Available fire modes, rates, burst settings
- **Reliability**: Malfunction rates, environmental factors
- **Ammunition**: Compatible bullet types and magazine settings
- **Physics**: Mathematical vs artistic ballistics settings
## Setting Up a New Weapon
### 1. Create Weapon Configuration Asset
1. In Content Browser, right-click and navigate to **Ballistics → Weapon Configuration**
2. Name it based on your weapon (e.g., "M4_Carbine_Config")
3. The factory will auto-configure based on the name:
- M4/AR15 → Modern assault rifle settings
- AK → AK-type weapon settings
- Pistol/Glock/1911 → Handgun settings
- Sniper/Bolt → Bolt-action rifle settings
- Shotgun → Shotgun settings
- LMG/Machine → Light machine gun settings
### 2. Configure Your Weapon
Open the weapon configuration asset and customize:
**Weapon Info:**
```
Weapon Name: "M4 Carbine"
Manufacturer: "Colt"
Model: "M4A1"
Caliber: "5.56x45mm NATO"
```
**Fire Control:**
```
Available Fire Modes: [Semi-Auto, Full Auto]
Default Fire Mode: Semi-Auto
Fire Rate Min: 700 RPM
Fire Rate Max: 950 RPM
Burst Count: 3
```
**Barrel Configuration:**
```
Barrel Length: 14.5 inches
Muzzle Velocity: 91440 cm/s (~3000 fps)
Inherent Accuracy: 0.001 radians
Spread Max: 0.001 radians
```
### 3. Create Gun Blueprint
1. Create a new Blueprint based on **EBGun**
2. Set the **Weapon Config** to your configuration asset
3. Configure the gun mesh and sockets
4. Set up animations for firing, reloading, etc.
### 4. Set Up Ammunition
1. Create **Bullet Properties Assets** for your ammunition types
2. Add them to the weapon configuration's **Compatible Bullet Types**
3. Set the **Default Bullet Type**
## Blueprint Usage
### Basic Firing
```cpp
// Event: Player Input Trigger Pressed
Gun->PullTrigger();
// Event: Player Input Trigger Released
Gun->ReleaseTrigger();
```
### Ammunition Management
```cpp
// Load magazine with specific bullet types
TArray<UEBBulletPropertiesAsset*> Ammo;
Ammo.Add(M855_Bullet);
Ammo.Add(M193_Bullet);
Gun->LoadMagazine(Ammo);
// Check ammunition status
int32 RoundsLeft = Gun->GetRoundsInMagazine();
int32 TotalRounds = Gun->GetTotalRounds();
bool CanShoot = Gun->CanFire();
```
### Weapon Controls
```cpp
// Safety controls
Gun->SetSafety(true); // Safety on
Gun->SetSafety(false); // Safety off
// Manual cycling (bolt-actions, pump shotguns)
Gun->ChargingHandle(); // Cycle action
// Fire mode selection
Gun->SetFireMode(EFireMode::FM_Burst);
```
### Events
```cpp
// Gun Events
Gun->OnGunFired.AddDynamic(this, &AMyWeapon::OnGunFired);
Gun->OnMagazineEmpty.AddDynamic(this, &AMyWeapon::OnMagazineEmpty);
Gun->OnGunStateChanged.AddDynamic(this, &AMyWeapon::OnStateChanged);
// Magazine Events
Magazine->OnBulletFed.AddDynamic(this, &AMyWeapon::OnBulletFed);
Magazine->OnMagazineJammed.AddDynamic(this, &AMyWeapon::OnJammed);
```
## Advanced Features
### Custom Magazine Types
```cpp
// Belt-fed machine gun
MagazineConfig.MagazineType = EMagazineType::MT_Belt;
MagazineConfig.MaxCapacity = 200;
MagazineConfig.FeedRate = 0.05f; // Fast feeding
// Tube magazine shotgun
MagazineConfig.MagazineType = EMagazineType::MT_Tube;
MagazineConfig.MaxCapacity = 8;
MagazineConfig.bAllowMixedBulletTypes = true; // Different shell types
```
### Reliability Simulation
```cpp
// Environmental effects
ReliabilityConfig.DirtSensitivity = 0.3f;
ReliabilityConfig.TemperatureSensitivity = 0.002f;
ReliabilityConfig.MinOperatingTemp = -30.0f;
ReliabilityConfig.MaxOperatingTemp = 55.0f;
// Wear and maintenance
ReliabilityConfig.WearFactor = 0.0001f;
ReliabilityConfig.ServiceLife = 15000; // rounds
```
### Multi-Barrel Systems
```cpp
// For weapons with multiple barrels (like gatling guns)
BarrelConfig.GatlingSpoolUpTime = 2.0f;
BarrelConfig.GatlingSpoolDownTime = 3.0f;
FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Gatling);
```
## Migration from Old System
### Before (Scattered Settings)
```cpp
// Settings spread across multiple components
Barrel->FireRate = 650.0f;
Barrel->Spread = 0.001f;
Barrel->MuzzleVelocity = 91440.0f;
Bullet->Mass = 0.004f;
Bullet->Diameter = 0.556f;
MaterialMap->PenetrationDepth = 15.0f;
```
### After (Centralized Configuration)
```cpp
// All settings in one place
WeaponConfig->FireControlConfig.FireRateMax = 650.0f;
WeaponConfig->BarrelConfig.SpreadMax = 0.001f;
WeaponConfig->BarrelConfig.MuzzleVelocityMax = 91440.0f;
WeaponConfig->DefaultBulletType = MyBulletAsset;
WeaponConfig->MaterialResponseMap = MyMaterialMap;
// Apply to gun
Gun->ApplyWeaponConfiguration(WeaponConfig);
```
## Benefits of New Architecture
1. **Centralized Settings**: All weapon parameters in one place
2. **Realistic Simulation**: Proper magazine, safety, and malfunction systems
3. **Modular Design**: Easy to swap magazines, barrels, configurations
4. **Better Workflow**: Intuitive gun-first approach
5. **Reduced Complexity**: No more hunting for scattered settings
6. **Multiplayer Ready**: Built-in replication and authority handling
7. **Asset Reuse**: Share configurations between similar weapons
## Best Practices
1. **Use Weapon Configurations**: Always create configuration assets for your weapons
2. **Group Similar Weapons**: Share configurations between variants (M4A1, M4A3, etc.)
3. **Plan Magazine Types**: Choose appropriate magazine types for realism
4. **Test Reliability**: Adjust malfunction rates for your game's difficulty
5. **Configure Events**: Use the event system for UI updates and effects
6. **Consider Physics Mode**: Choose mathematical vs artistic based on your needs
This new architecture provides a much more intuitive and consolidated approach to weapon creation while maintaining all the advanced ballistics features of EasyBallistics.
+6
View File
@@ -35,5 +35,11 @@
"Mac"
]
}
],
"Plugins": [
{
"Name": "EditorScriptingUtilities",
"Enabled": true
}
]
}
@@ -22,7 +22,8 @@ public class EasyBallistics : ModuleRules
{
"CoreUObject",
"Engine",
"PhysicsCore"
"PhysicsCore",
"NetCore"
// ... add private dependencies that you statically link with here ...
}
);
+151
View File
@@ -2,6 +2,10 @@
#include "EBBarrel.h"
#include "EBBullet.h"
#include "EngineUtils.h"
#include "EBGun.h"
#include "EBMagazine.h"
#include "EBWeaponConfiguration.h"
#include "EBBulletProperties.h"
UEBBarrel::UEBBarrel() {
PrimaryComponentTick.bCanEverTick = true;
@@ -327,3 +331,150 @@ FString UEBBarrel::GetBulletDebugInfo() const
return DebugInfo;
}
void UEBBarrel::SetParentGun(AEBGun* Gun)
{
ParentGun = Gun;
}
void UEBBarrel::ApplyBarrelConfiguration(const FBarrelConfiguration& BarrelConfig)
{
// Apply barrel physical properties
BarrelLength = BarrelConfig.BarrelLength;
RiflingTwist = BarrelConfig.RiflingTwist;
BoreRadius = BarrelConfig.BoreRadius;
MuzzleVelocityMin = BarrelConfig.MuzzleVelocityMin;
MuzzleVelocityMax = BarrelConfig.MuzzleVelocityMax;
// Apply ballistic properties
Spread = BarrelConfig.SpreadMax;
SpreadBias = BarrelConfig.SpreadBias;
MuzzleVelocityMultiplierMin = 1.0f - BarrelConfig.MuzzleVelocityVariation;
MuzzleVelocityMultiplierMax = 1.0f + BarrelConfig.MuzzleVelocityVariation;
// Apply recoil properties
RecoilMultiplier = BarrelConfig.RecoilMultiplier;
UE_LOG(LogTemp, Log, TEXT("Barrel Configuration Applied - Length: %.1f, Twist: 1:%.1f, Velocity: %.0f-%.0f cm/s"),
BarrelLength, RiflingTwist, MuzzleVelocityMin, MuzzleVelocityMax);
}
UEBMagazine* UEBBarrel::GetConnectedMagazine() const
{
if (ParentGun)
{
return ParentGun->Magazine;
}
return nullptr;
}
UEBBulletPropertiesAsset* UEBBarrel::GetChamberedBulletType() const
{
if (ParentGun)
{
return ParentGun->GetChamberedBulletType();
}
return nullptr;
}
float UEBBarrel::CalculateEffectiveMuzzleVelocity(UEBBulletPropertiesAsset* BulletType) const
{
if (!BulletType)
{
return MuzzleVelocityMin;
}
// Base velocity from barrel configuration
float BaseVelocity = FMath::RandRange(MuzzleVelocityMin, MuzzleVelocityMax);
// Apply velocity multiplier variation
float VelocityMultiplier = FMath::RandRange(MuzzleVelocityMultiplierMin, MuzzleVelocityMultiplierMax);
BaseVelocity *= VelocityMultiplier;
// Barrel length affects velocity (longer barrels = higher velocity, up to a point)
float LengthFactor = FMath::Clamp((BarrelLength - 10.0f) / 20.0f, 0.8f, 1.2f);
BaseVelocity *= LengthFactor;
// Bullet weight affects velocity (heavier bullets = lower velocity)
float BulletMass = BulletType->BulletProperties.GetMassKg();
float MassFactor = FMath::Clamp(0.004f / BulletMass, 0.7f, 1.3f); // Normalized around 4g bullet
BaseVelocity *= MassFactor;
return BaseVelocity;
}
float UEBBarrel::CalculateBarrelAccuracy() const
{
// Base accuracy from spread
float BaseAccuracy = Spread;
// Barrel length improves accuracy
float LengthFactor = FMath::Clamp(BarrelLength / 20.0f, 0.5f, 1.5f);
BaseAccuracy /= LengthFactor;
// Rifling twist affects accuracy (optimal twist varies by bullet)
// For now, assume 1:7 to 1:9 is optimal for 5.56mm
float TwistOptimal = 8.0f;
float TwistFactor = 1.0f - FMath::Abs(RiflingTwist - TwistOptimal) * 0.01f;
TwistFactor = FMath::Clamp(TwistFactor, 0.8f, 1.2f);
BaseAccuracy /= TwistFactor;
return BaseAccuracy;
}
FVector UEBBarrel::CalculateRecoilImpulse(UEBBulletPropertiesAsset* BulletType) const
{
if (!BulletType)
{
return FVector::ZeroVector;
}
// Calculate recoil based on bullet momentum
float BulletMass = BulletType->BulletProperties.GetMassKg();
float MuzzleVelocity = CalculateEffectiveMuzzleVelocity(BulletType);
float Momentum = BulletMass * (MuzzleVelocity / 100.0f); // Convert cm/s to m/s
// Base recoil impulse
float RecoilForce = Momentum * RecoilMultiplier;
// Barrel length affects recoil (longer barrels spread recoil over more time, but same total impulse)
float LengthFactor = FMath::Clamp(20.0f / BarrelLength, 0.8f, 1.5f);
RecoilForce *= LengthFactor;
// Direction from weapon config (if parent gun has config)
FVector RecoilDirection = FVector(0, 0, 1); // Default upward
if (ParentGun && ParentGun->WeaponConfig)
{
RecoilDirection = ParentGun->WeaponConfig->BarrelConfig.RecoilDirection;
}
return RecoilDirection * RecoilForce;
}
int UEBBarrel::GetMagazineRoundCount() const
{
UEBMagazine* Magazine = GetConnectedMagazine();
if (Magazine)
{
return Magazine->GetCurrentRoundCount();
}
return 0;
}
bool UEBBarrel::HasChamberedRound() const
{
if (ParentGun)
{
return ParentGun->IsLoaded();
}
return false;
}
bool UEBBarrel::CanFireFromGun() const
{
if (ParentGun)
{
return ParentGun->CanFire();
}
return false;
}
+693
View File
@@ -0,0 +1,693 @@
// Copyright 2016 Mookie. All Rights Reserved.
#include "EBGun.h"
#include "EBMagazine.h"
#include "EBWeaponConfiguration.h"
#include "EBBullet.h"
#include "Net/UnrealNetwork.h"
#include "Engine/Engine.h"
#include "TimerManager.h"
AEBGun::AEBGun()
{
PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
SetReplicateMovement(true);
// Create the gun mesh component
GunMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("GunMesh"));
RootComponent = GunMesh;
// Create the barrel component
Barrel = CreateDefaultSubobject<UEBBarrel>(TEXT("Barrel"));
Barrel->SetupAttachment(GunMesh, TEXT("MuzzleSocket"));
Barrel->SetParentGun(this);
// Create the magazine component
Magazine = CreateDefaultSubobject<UEBMagazine>(TEXT("Magazine"));
// Initialize state
CurrentGunState = EGunState::GS_Ready;
PreviousGunState = EGunState::GS_Ready;
bSafetyOn = false;
bChamberedRound = false;
bHammerCocked = false;
bTriggerPressed = false;
bCanProcessNextShot = true;
BurstShotsFired = 0;
ChamberedBulletType = nullptr;
}
void AEBGun::BeginPlay()
{
Super::BeginPlay();
// Apply weapon configuration if available
if (WeaponConfig)
{
ApplyWeaponConfiguration(WeaponConfig);
}
// Initialize magazine
if (Magazine)
{
Magazine->OnMagazineEmpty.AddDynamic(this, &AEBGun::OnMagazineEmptyHandler);
Magazine->OnBulletFed.AddDynamic(this, &AEBGun::OnBulletFedHandler);
}
// Initialize barrel
if (Barrel)
{
Barrel->ShotFired.AddDynamic(this, &AEBGun::OnBarrelShotFiredHandler);
}
}
void AEBGun::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Process firing state
if (CurrentGunState == EGunState::GS_Firing)
{
ProcessFiring();
}
}
void AEBGun::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AEBGun, CurrentGunState);
DOREPLIFETIME(AEBGun, bSafetyOn);
DOREPLIFETIME(AEBGun, bChamberedRound);
DOREPLIFETIME(AEBGun, bHammerCocked);
DOREPLIFETIME(AEBGun, ChamberedBulletType);
}
void AEBGun::PullTrigger()
{
if (HasAuthority())
{
ServerPullTrigger();
}
else
{
ServerPullTrigger();
}
}
void AEBGun::ServerPullTrigger_Implementation()
{
if (!CanFire())
{
return;
}
bTriggerPressed = true;
if (CurrentGunState == EGunState::GS_Ready)
{
SetGunState(EGunState::GS_Firing);
}
}
void AEBGun::ReleaseTrigger()
{
if (HasAuthority())
{
ServerReleaseTrigger();
}
else
{
ServerReleaseTrigger();
}
}
void AEBGun::ServerReleaseTrigger_Implementation()
{
bTriggerPressed = false;
// Handle fire mode specific behavior
if (Barrel && WeaponConfig)
{
EFireMode CurrentFireMode = Barrel->FireMode;
if (CurrentFireMode == EFireMode::FM_Semiauto || CurrentFireMode == EFireMode::FM_Manual)
{
if (CurrentGunState == EGunState::GS_Firing)
{
SetGunState(EGunState::GS_Ready);
}
}
}
}
void AEBGun::SetSafety(bool bNewSafetyState)
{
if (HasAuthority())
{
ServerSetSafety(bNewSafetyState);
}
else
{
ServerSetSafety(bNewSafetyState);
}
}
void AEBGun::ServerSetSafety_Implementation(bool bNewSafetyState)
{
if (!WeaponConfig || !WeaponConfig->FireControlConfig.bHasSafety)
{
return;
}
bool bOldSafetyState = bSafetyOn;
bSafetyOn = bNewSafetyState;
if (bSafetyOn && CurrentGunState == EGunState::GS_Firing)
{
SetGunState(EGunState::GS_SafetyOn);
}
else if (!bSafetyOn && CurrentGunState == EGunState::GS_SafetyOn)
{
SetGunState(EGunState::GS_Ready);
}
if (bOldSafetyState != bSafetyOn)
{
OnSafetyChanged.Broadcast(bSafetyOn);
MulticastOnSafetyChanged(bSafetyOn);
}
}
void AEBGun::CockHammer()
{
if (HasAuthority())
{
bHammerCocked = true;
}
}
void AEBGun::ReleaseHammer()
{
if (HasAuthority())
{
bHammerCocked = false;
}
}
void AEBGun::LoadMagazine(TArray<UEBBulletPropertiesAsset*> BulletTypes)
{
if (Magazine)
{
Magazine->LoadBullets(BulletTypes);
}
}
void AEBGun::EjectMagazine()
{
if (HasAuthority())
{
ServerEjectMagazine();
}
else
{
ServerEjectMagazine();
}
}
void AEBGun::ServerEjectMagazine_Implementation()
{
if (Magazine)
{
Magazine->UnloadAllBullets();
}
OnMagazineChanged.Broadcast(nullptr);
MulticastOnMagazineChanged(nullptr);
}
void AEBGun::InsertMagazine(UEBMagazine* NewMagazine)
{
if (HasAuthority())
{
ServerInsertMagazine(NewMagazine);
}
else
{
ServerInsertMagazine(NewMagazine);
}
}
void AEBGun::ServerInsertMagazine_Implementation(UEBMagazine* NewMagazine)
{
if (!NewMagazine)
{
return;
}
// Replace magazine component (in a real implementation, this would be more complex)
Magazine = NewMagazine;
OnMagazineChanged.Broadcast(NewMagazine);
MulticastOnMagazineChanged(NewMagazine);
}
void AEBGun::ChargingHandle()
{
if (HasAuthority())
{
ServerChargingHandle();
}
else
{
ServerChargingHandle();
}
}
void AEBGun::ServerChargingHandle_Implementation()
{
// Eject chambered round if present
if (bChamberedRound)
{
ChamberedBulletType = nullptr;
bChamberedRound = false;
}
// Feed new round from magazine
if (Magazine && Magazine->CanFeed())
{
UEBBulletPropertiesAsset* NewBullet = Magazine->FeedNextBullet();
if (NewBullet)
{
ChamberedBulletType = NewBullet;
bChamberedRound = true;
}
}
// Update gun state
if (CurrentGunState == EGunState::GS_Empty && bChamberedRound)
{
SetGunState(EGunState::GS_Ready);
}
else if (!bChamberedRound && (!Magazine || Magazine->bIsEmpty))
{
SetGunState(EGunState::GS_Empty);
}
}
void AEBGun::EjectChamberedRound()
{
if (HasAuthority())
{
if (bChamberedRound)
{
ChamberedBulletType = nullptr;
bChamberedRound = false;
}
}
}
bool AEBGun::CanFire() const
{
if (bSafetyOn)
{
return false;
}
if (CurrentGunState == EGunState::GS_Jammed ||
CurrentGunState == EGunState::GS_Empty ||
CurrentGunState == EGunState::GS_SafetyOn ||
CurrentGunState == EGunState::GS_Malfunction)
{
return false;
}
if (!bChamberedRound || !ChamberedBulletType)
{
return false;
}
return true;
}
bool AEBGun::IsLoaded() const
{
return bChamberedRound && ChamberedBulletType != nullptr;
}
int32 AEBGun::GetRoundsInMagazine() const
{
if (Magazine)
{
return Magazine->GetCurrentRoundCount();
}
return 0;
}
int32 AEBGun::GetTotalRounds() const
{
int32 TotalRounds = 0;
if (Magazine)
{
TotalRounds += Magazine->GetCurrentRoundCount();
}
if (bChamberedRound)
{
TotalRounds += 1;
}
return TotalRounds;
}
UEBBulletPropertiesAsset* AEBGun::GetChamberedBulletType() const
{
return ChamberedBulletType;
}
void AEBGun::ApplyWeaponConfiguration(UEBWeaponConfiguration* NewConfig)
{
if (!NewConfig)
{
return;
}
WeaponConfig = NewConfig;
// Apply configuration to barrel
if (Barrel)
{
// Set parent gun reference
Barrel->SetParentGun(this);
// Apply barrel-specific configuration
Barrel->ApplyBarrelConfiguration(NewConfig->BarrelConfig);
// Apply fire control configuration
Barrel->FireMode = NewConfig->FireControlConfig.DefaultFireMode;
Barrel->FireRateMin = NewConfig->FireControlConfig.FireRateMin / 60.0f; // Convert RPM to RPS
Barrel->FireRateMax = NewConfig->FireControlConfig.FireRateMax / 60.0f;
Barrel->BurstCount = NewConfig->FireControlConfig.BurstCount;
Barrel->BurstCooldown = NewConfig->FireControlConfig.BurstCooldown;
Barrel->GatlingSpoolUpTime = NewConfig->FireControlConfig.GatlingSpoolUpTime;
Barrel->GatlingSpoolDownTime = NewConfig->FireControlConfig.GatlingSpoolDownTime;
Barrel->GatlingAutoSpool = NewConfig->FireControlConfig.bGatlingAutoSpool;
// Apply networking configuration
Barrel->ReplicateVariables = NewConfig->bReplicateVariables;
Barrel->ReplicateShotFiredEvents = NewConfig->bReplicateShotEvents;
Barrel->ClientSideAim = NewConfig->bClientSideAim;
// Disable legacy ammo system in favor of gun's magazine system
Barrel->CycleAmmo = false;
UE_LOG(LogTemp, Log, TEXT("Gun '%s' applied configuration to barrel"), *NewConfig->GetWeaponDisplayName());
}
// Apply configuration to magazine
if (Magazine)
{
Magazine->MagazineConfig.MaxCapacity = NewConfig->DefaultMagazineCapacity;
Magazine->MagazineConfig.MagazineType = NewConfig->DefaultMagazineType;
Magazine->MagazineConfig.bAllowMixedBulletTypes = NewConfig->bAllowMixedAmmo;
Magazine->MagazineConfig.MalfunctionChance = NewConfig->ReliabilityConfig.MalfunctionRate;
}
// Set default safety state
if (NewConfig->FireControlConfig.bHasSafety)
{
bSafetyOn = NewConfig->FireControlConfig.bDefaultSafetyOn;
}
else
{
bSafetyOn = false;
}
}
void AEBGun::SetFireMode(EFireMode NewFireMode)
{
if (HasAuthority())
{
ServerSetFireMode(NewFireMode);
}
else
{
ServerSetFireMode(NewFireMode);
}
}
void AEBGun::ServerSetFireMode_Implementation(EFireMode NewFireMode)
{
if (!WeaponConfig || !WeaponConfig->IsFireModeAvailable(NewFireMode))
{
return;
}
if (Barrel)
{
Barrel->FireMode = NewFireMode;
}
}
EFireMode AEBGun::GetFireMode() const
{
if (Barrel)
{
return Barrel->FireMode;
}
return EFireMode::FM_Semiauto;
}
void AEBGun::SetGunState(EGunState NewState)
{
if (!HasAuthority())
{
return;
}
if (CurrentGunState == NewState)
{
return;
}
PreviousGunState = CurrentGunState;
CurrentGunState = NewState;
OnGunStateChanged.Broadcast(NewState);
OnGunStateChangedInternal(PreviousGunState, NewState);
}
void AEBGun::OnRep_GunState()
{
OnGunStateChanged.Broadcast(CurrentGunState);
OnGunStateChangedInternal(PreviousGunState, CurrentGunState);
}
void AEBGun::ProcessFiring()
{
if (!HasAuthority())
{
return;
}
if (!CanFire() || !bCanProcessNextShot)
{
return;
}
// Check if we should continue firing based on fire mode
if (Barrel && WeaponConfig)
{
EFireMode CurrentFireMode = Barrel->FireMode;
switch (CurrentFireMode)
{
case EFireMode::FM_Semiauto:
case EFireMode::FM_Manual:
if (!bTriggerPressed)
{
SetGunState(EGunState::GS_Ready);
return;
}
break;
case EFireMode::FM_Auto:
if (!bTriggerPressed)
{
SetGunState(EGunState::GS_Ready);
return;
}
break;
case EFireMode::FM_Burst:
case EFireMode::FM_InterBurst:
if (BurstShotsFired >= WeaponConfig->FireControlConfig.BurstCount)
{
BurstShotsFired = 0;
SetGunState(EGunState::GS_Ready);
return;
}
break;
}
}
// Fire the weapon
FireShot();
}
void AEBGun::FireShot()
{
if (!HasAuthority() || !CanFire())
{
return;
}
// Fire through barrel
if (Barrel && ChamberedBulletType)
{
// Set bullet class on barrel (this is a simplified approach)
// In a real implementation, you would need to create a bullet and configure it properly
Barrel->Ammo.Empty();
if (ChamberedBulletType)
{
// For now, we'll use the default bullet class since we can't get the class from the asset
// This would need to be properly implemented with a bullet class reference in the asset
}
// Fire the shot
Barrel->Shoot(true);
// Clear chambered round
ChamberedBulletType = nullptr;
bChamberedRound = false;
// Update burst count
BurstShotsFired++;
// Set fire rate cooldown
float FireRate = WeaponConfig ? WeaponConfig->GetEffectiveFireRate(GetFireMode()) : 600.0f;
float CooldownTime = 60.0f / FireRate; // Convert RPM to seconds
bCanProcessNextShot = false;
GetWorld()->GetTimerManager().SetTimer(FireRateTimer, [this]()
{
bCanProcessNextShot = true;
}, CooldownTime, false);
// Try to feed next round
if (!FeedNextRound())
{
SetGunState(EGunState::GS_Empty);
}
}
}
bool AEBGun::FeedNextRound()
{
if (!HasAuthority())
{
return false;
}
if (Magazine && Magazine->CanFeed())
{
UEBBulletPropertiesAsset* NextBullet = Magazine->FeedNextBullet();
if (NextBullet)
{
ChamberedBulletType = NextBullet;
bChamberedRound = true;
return true;
}
}
return false;
}
void AEBGun::OnMagazineEmptyHandler(UEBMagazine* EmptyMagazine)
{
OnMagazineEmpty.Broadcast();
if (!bChamberedRound)
{
SetGunState(EGunState::GS_Empty);
}
}
void AEBGun::OnBulletFedHandler(UEBMagazine* SourceMagazine, UEBBulletPropertiesAsset* BulletType)
{
// Handle bullet fed from magazine
}
void AEBGun::OnBarrelShotFiredHandler()
{
OnGunFired.Broadcast(ChamberedBulletType);
MulticastOnGunFired(ChamberedBulletType);
}
void AEBGun::MulticastOnGunFired_Implementation(UEBBulletPropertiesAsset* BulletType)
{
if (!HasAuthority())
{
OnGunFired.Broadcast(BulletType);
}
}
void AEBGun::MulticastOnMagazineChanged_Implementation(UEBMagazine* NewMagazine)
{
if (!HasAuthority())
{
OnMagazineChanged.Broadcast(NewMagazine);
}
}
void AEBGun::MulticastOnSafetyChanged_Implementation(bool bNewSafetyState)
{
if (!HasAuthority())
{
OnSafetyChanged.Broadcast(bNewSafetyState);
}
}
// Barrel Integration Functions
float AEBGun::GetBarrelLength() const
{
if (Barrel)
{
return Barrel->BarrelLength;
}
return 0.0f;
}
float AEBGun::GetEffectiveMuzzleVelocity() const
{
if (Barrel && ChamberedBulletType)
{
return Barrel->CalculateEffectiveMuzzleVelocity(ChamberedBulletType);
}
return 0.0f;
}
float AEBGun::GetBarrelAccuracy() const
{
if (Barrel)
{
return Barrel->CalculateBarrelAccuracy();
}
return 0.0f;
}
FVector AEBGun::CalculateRecoilImpulse() const
{
if (Barrel && ChamberedBulletType)
{
return Barrel->CalculateRecoilImpulse(ChamberedBulletType);
}
return FVector::ZeroVector;
}
bool AEBGun::IsBarrelConnected() const
{
return Barrel != nullptr && Barrel->IsConnectedToGun();
}
@@ -0,0 +1,405 @@
// Copyright 2016 Mookie. All Rights Reserved.
#include "EBMagazine.h"
#include "Net/UnrealNetwork.h"
#include "Engine/Engine.h"
UEBMagazine::UEBMagazine()
{
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(true);
// Initialize magazine state
CurrentCapacity = 0;
bIsEmpty = true;
bIsFull = false;
bIsJammed = false;
bBoltHeldOpen = false;
}
void UEBMagazine::BeginPlay()
{
Super::BeginPlay();
UpdateMagazineState();
}
void UEBMagazine::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UEBMagazine, StoredBullets);
DOREPLIFETIME(UEBMagazine, CurrentCapacity);
DOREPLIFETIME(UEBMagazine, bIsEmpty);
DOREPLIFETIME(UEBMagazine, bIsFull);
DOREPLIFETIME(UEBMagazine, bIsJammed);
DOREPLIFETIME(UEBMagazine, bBoltHeldOpen);
}
void UEBMagazine::LoadBullets(TArray<UEBBulletPropertiesAsset*> BulletTypes)
{
if (GetOwner()->HasAuthority())
{
ServerLoadBullets(BulletTypes);
}
else
{
ServerLoadBullets(BulletTypes);
}
}
void UEBMagazine::ServerLoadBullets_Implementation(const TArray<UEBBulletPropertiesAsset*>& BulletTypes)
{
if (bIsJammed)
{
return;
}
for (UEBBulletPropertiesAsset* BulletType : BulletTypes)
{
if (BulletType && CurrentCapacity < MagazineConfig.MaxCapacity)
{
StoredBullets.Add(BulletType);
CurrentCapacity++;
OnBulletLoaded.Broadcast(this, BulletType);
MulticastOnBulletLoaded(BulletType);
}
}
UpdateMagazineState();
}
void UEBMagazine::LoadSingleBullet(UEBBulletPropertiesAsset* BulletType)
{
if (GetOwner()->HasAuthority())
{
ServerLoadSingleBullet(BulletType);
}
else
{
ServerLoadSingleBullet(BulletType);
}
}
void UEBMagazine::ServerLoadSingleBullet_Implementation(UEBBulletPropertiesAsset* BulletType)
{
if (!BulletType || bIsJammed || bIsFull)
{
return;
}
StoredBullets.Add(BulletType);
CurrentCapacity++;
OnBulletLoaded.Broadcast(this, BulletType);
MulticastOnBulletLoaded(BulletType);
UpdateMagazineState();
}
UEBBulletPropertiesAsset* UEBMagazine::FeedNextBullet()
{
if (!GetOwner()->HasAuthority())
{
return nullptr;
}
if (!CanFeed())
{
return nullptr;
}
// Check for malfunction
if (CheckForMalfunction())
{
bIsJammed = true;
OnMagazineJammed.Broadcast(this);
MulticastOnMagazineJammed();
UpdateMagazineState();
return nullptr;
}
// Feed the bullet
UEBBulletPropertiesAsset* FedBullet = StoredBullets[0];
StoredBullets.RemoveAt(0);
CurrentCapacity--;
OnBulletFed.Broadcast(this, FedBullet);
MulticastOnBulletFed(FedBullet);
UpdateMagazineState();
// Check for bolt hold open
if (bIsEmpty && MagazineConfig.bLastRoundBoltHoldOpen)
{
ProcessBoltHoldOpen();
}
return FedBullet;
}
void UEBMagazine::UnloadAllBullets()
{
if (GetOwner()->HasAuthority())
{
ServerUnloadAllBullets();
}
else
{
ServerUnloadAllBullets();
}
}
void UEBMagazine::ServerUnloadAllBullets_Implementation()
{
StoredBullets.Empty();
CurrentCapacity = 0;
bBoltHeldOpen = false;
UpdateMagazineState();
}
UEBBulletPropertiesAsset* UEBMagazine::UnloadLastBullet()
{
if (!GetOwner()->HasAuthority())
{
return nullptr;
}
if (bIsEmpty)
{
return nullptr;
}
UEBBulletPropertiesAsset* UnloadedBullet = StoredBullets.Last();
StoredBullets.RemoveAt(StoredBullets.Num() - 1);
CurrentCapacity--;
UpdateMagazineState();
return UnloadedBullet;
}
void UEBMagazine::ClearJam()
{
if (GetOwner()->HasAuthority())
{
ServerClearJam();
}
else
{
ServerClearJam();
}
}
void UEBMagazine::ServerClearJam_Implementation()
{
if (bIsJammed)
{
bIsJammed = false;
UpdateMagazineState();
}
}
void UEBMagazine::ReleaseBoltHold()
{
if (GetOwner()->HasAuthority())
{
ServerReleaseBoltHold();
}
else
{
ServerReleaseBoltHold();
}
}
void UEBMagazine::ServerReleaseBoltHold_Implementation()
{
if (bBoltHeldOpen)
{
bBoltHeldOpen = false;
UpdateMagazineState();
}
}
void UEBMagazine::ForceJam()
{
if (GetOwner()->HasAuthority())
{
bIsJammed = true;
OnMagazineJammed.Broadcast(this);
MulticastOnMagazineJammed();
UpdateMagazineState();
}
}
int32 UEBMagazine::GetRemainingCapacity() const
{
return MagazineConfig.MaxCapacity - CurrentCapacity;
}
int32 UEBMagazine::GetCurrentRoundCount() const
{
return CurrentCapacity;
}
int32 UEBMagazine::GetMaxCapacity() const
{
return MagazineConfig.MaxCapacity;
}
float UEBMagazine::GetFillPercentage() const
{
if (MagazineConfig.MaxCapacity <= 0)
{
return 0.0f;
}
return (float)CurrentCapacity / (float)MagazineConfig.MaxCapacity;
}
UEBBulletPropertiesAsset* UEBMagazine::PeekNextBullet() const
{
if (bIsEmpty || StoredBullets.Num() == 0)
{
return nullptr;
}
return StoredBullets[0];
}
bool UEBMagazine::CanFeed() const
{
return !bIsEmpty && !bIsJammed && !bBoltHeldOpen && StoredBullets.Num() > 0;
}
bool UEBMagazine::NeedsReload() const
{
return bIsEmpty || CurrentCapacity < (MagazineConfig.MaxCapacity * 0.25f); // Need reload when below 25%
}
TArray<UEBBulletPropertiesAsset*> UEBMagazine::GetBulletTypesSummary() const
{
TArray<UEBBulletPropertiesAsset*> UniqueBulletTypes;
for (UEBBulletPropertiesAsset* BulletType : StoredBullets)
{
if (BulletType && !UniqueBulletTypes.Contains(BulletType))
{
UniqueBulletTypes.Add(BulletType);
}
}
return UniqueBulletTypes;
}
void UEBMagazine::UpdateMagazineState()
{
bool bOldEmpty = bIsEmpty;
bool bOldFull = bIsFull;
bIsEmpty = (CurrentCapacity == 0);
bIsFull = (CurrentCapacity >= MagazineConfig.MaxCapacity);
// Fire events for state changes
if (bIsEmpty && !bOldEmpty)
{
OnMagazineEmpty.Broadcast(this);
}
else if (bIsFull && !bOldFull)
{
OnMagazineFull.Broadcast(this);
}
}
bool UEBMagazine::CheckForMalfunction()
{
if (MagazineConfig.MalfunctionChance <= 0.0f)
{
return false;
}
float RandomValue = FMath::RandRange(0.0f, 1.0f);
return RandomValue <= MagazineConfig.MalfunctionChance;
}
void UEBMagazine::ProcessBoltHoldOpen()
{
if (!bBoltHeldOpen)
{
bBoltHeldOpen = true;
OnBoltHoldOpen.Broadcast(this);
MulticastOnBoltHoldOpen();
}
}
void UEBMagazine::OnRep_StoredBullets()
{
UpdateMagazineState();
}
void UEBMagazine::OnRep_CurrentCapacity()
{
UpdateMagazineState();
}
void UEBMagazine::OnRep_IsEmpty()
{
if (bIsEmpty)
{
OnMagazineEmpty.Broadcast(this);
}
}
void UEBMagazine::OnRep_IsFull()
{
if (bIsFull)
{
OnMagazineFull.Broadcast(this);
}
}
void UEBMagazine::OnRep_IsJammed()
{
if (bIsJammed)
{
OnMagazineJammed.Broadcast(this);
}
}
void UEBMagazine::OnRep_BoltHeldOpen()
{
if (bBoltHeldOpen)
{
OnBoltHoldOpen.Broadcast(this);
}
}
void UEBMagazine::MulticastOnBulletFed_Implementation(UEBBulletPropertiesAsset* BulletType)
{
if (!GetOwner()->HasAuthority())
{
OnBulletFed.Broadcast(this, BulletType);
}
}
void UEBMagazine::MulticastOnBulletLoaded_Implementation(UEBBulletPropertiesAsset* BulletType)
{
if (!GetOwner()->HasAuthority())
{
OnBulletLoaded.Broadcast(this, BulletType);
}
}
void UEBMagazine::MulticastOnMagazineJammed_Implementation()
{
if (!GetOwner()->HasAuthority())
{
OnMagazineJammed.Broadcast(this);
}
}
void UEBMagazine::MulticastOnBoltHoldOpen_Implementation()
{
if (!GetOwner()->HasAuthority())
{
OnBoltHoldOpen.Broadcast(this);
}
}
@@ -0,0 +1,82 @@
// Copyright 2016 Mookie. All Rights Reserved.
#include "EBWeaponConfiguration.h"
#include "EBBulletProperties.h"
#include "EBGun.h"
bool UEBWeaponConfiguration::IsFireModeAvailable(EFireMode FireMode) const
{
return FireControlConfig.AvailableFireModes.Contains(FireMode);
}
bool UEBWeaponConfiguration::IsBulletTypeCompatible(UEBBulletPropertiesAsset* BulletType) const
{
if (!BulletType)
{
return false;
}
// Check if it's the default bullet type
if (BulletType == DefaultBulletType)
{
return true;
}
// Check if it's in the compatible list
return CompatibleBulletTypes.Contains(BulletType);
}
float UEBWeaponConfiguration::GetEffectiveFireRate(EFireMode FireMode) const
{
// Base fire rate
float BaseFireRate = (FireControlConfig.FireRateMin + FireControlConfig.FireRateMax) / 2.0f;
// Adjust based on fire mode
switch (FireMode)
{
case EFireMode::FM_Semiauto:
return FMath::Min(BaseFireRate, 300.0f); // Cap semi-auto at 300 RPM
case EFireMode::FM_Auto:
return BaseFireRate;
case EFireMode::FM_Burst:
case EFireMode::FM_InterBurst:
return BaseFireRate * 1.2f; // Burst fire is slightly faster
case EFireMode::FM_Gatling:
return BaseFireRate * 2.0f; // Gatling guns fire much faster
case EFireMode::FM_Manual:
return 60.0f; // Manual actions are slow
case EFireMode::FM_Slamfire:
return BaseFireRate * 0.8f; // Slamfire is slightly slower
default:
return BaseFireRate;
}
}
FString UEBWeaponConfiguration::GetWeaponDisplayName() const
{
if (!Model.IsEmpty())
{
return FString::Printf(TEXT("%s %s"), *Manufacturer, *Model);
}
return WeaponName;
}
FString UEBWeaponConfiguration::GetFullWeaponName() const
{
FString FullName = GetWeaponDisplayName();
if (!Caliber.IsEmpty())
{
FullName += FString::Printf(TEXT(" (%s)"), *Caliber);
}
return FullName;
}
void UEBWeaponConfiguration::ApplyConfigurationToGun(AEBGun* Gun) const
{
if (Gun)
{
Gun->ApplyWeaponConfiguration(const_cast<UEBWeaponConfiguration*>(this));
}
}
+78 -23
View File
@@ -7,22 +7,11 @@
#include "Kismet/GameplayStatics.h"
#include "EBBullet.h"
#include "EBWeaponConfiguration.h"
#include "EBBarrel.generated.h"
UENUM(BlueprintType)
enum class EFireMode : uint8
{
FM_Auto UMETA(DisplayName = "Full Auto"),
FM_Semiauto UMETA(DisplayName = "Semiauto"),
FM_Burst UMETA(DisplayName = "Burst"),
FM_InterBurst UMETA(DisplayName = "Interruptible Burst"),
FM_Manual UMETA(DisplayName = "Manual"),
FM_Slamfire UMETA(DisplayName = "Slam Fire"),
FM_Gatling UMETA(DisplayName = "Gatling")
};
UCLASS(Blueprintable, ClassGroup = (Custom), hidecategories = (Object, LOD, Physics, Lighting, TextureStreaming, Collision, HLOD, Mobile, VirtualTexture, ComponentReplication), editinlinenew, meta = (BlueprintSpawnableComponent))
UCLASS(Blueprintable, ClassGroup = (Custom), hidecategories = (Object, LOD, Physics, Lighting, TextureStreaming, Collision, HLOD, Mobile, VirtualTexture, ComponentReplication), editinlinenew, meta = (BlueprintSpawnableComponent))
class EASYBALLISTICS_API UEBBarrel : public UPrimitiveComponent
{
@@ -32,6 +21,26 @@ public:
// Sets default values for this component's properties
UEBBarrel();
// Gun Integration
UPROPERTY(BlueprintReadOnly, Category = "Gun Integration")
class AEBGun* ParentGun;
// Barrel Physical Properties (from weapon config)
UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties")
float BarrelLength = 16.0f; // inches
UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties")
float RiflingTwist = 7.0f; // 1:X twist rate
UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties")
float BoreRadius = 0.112f; // inches
UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties")
float MuzzleVelocityMin = 91440.0f; // cm/s
UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties")
float MuzzleVelocityMax = 91440.0f; // cm/s
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
@@ -51,6 +60,35 @@ public:
UFUNCTION(BlueprintPure, Category = "EBBarrel|Debug")
FString GetBulletDebugInfo() const;
// Gun Integration Functions
UFUNCTION(BlueprintCallable, Category = "Gun Integration")
void SetParentGun(class AEBGun* Gun);
UFUNCTION(BlueprintPure, Category = "Gun Integration")
class AEBGun* GetParentGun() const { return ParentGun; }
UFUNCTION(BlueprintCallable, Category = "Gun Integration")
void ApplyBarrelConfiguration(const struct FBarrelConfiguration& BarrelConfig);
UFUNCTION(BlueprintPure, Category = "Gun Integration")
bool IsConnectedToGun() const { return ParentGun != nullptr; }
UFUNCTION(BlueprintPure, Category = "Gun Integration")
class UEBMagazine* GetConnectedMagazine() const;
UFUNCTION(BlueprintPure, Category = "Gun Integration")
class UEBBulletPropertiesAsset* GetChamberedBulletType() const;
// Enhanced Barrel Functions
UFUNCTION(BlueprintPure, Category = "Barrel Properties")
float CalculateEffectiveMuzzleVelocity(class UEBBulletPropertiesAsset* BulletType) const;
UFUNCTION(BlueprintPure, Category = "Barrel Properties")
float CalculateBarrelAccuracy() const;
UFUNCTION(BlueprintPure, Category = "Barrel Properties")
FVector CalculateRecoilImpulse(class UEBBulletPropertiesAsset* BulletType) const;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Bullet inherits barrel velocity, only works with physics enabled or with additional velocity set")) float InheritVelocity = 1.0f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Amount of recoil applied to the barrel, only works with physics enabled")) float RecoilMultiplier = 1.0f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Additional velocity, for use with InheritVelocity")) FVector AdditionalVelocity = FVector(0,0,0);
@@ -71,11 +109,17 @@ public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon") float GatlingSpoolDownTime = 1.0f;
UPROPERTY(BlueprintReadWrite, Category = "Weapon") float GatlingPhase = 0.0f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Ammo") bool CycleAmmo = true;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) bool CycleAmmoUnlimited = true;
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo") TArray<TSubclassOf<class AEBBullet>> Ammo;
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) int CycleAmmoCount;
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) int CycleAmmoPos;
// Legacy Ammo System (kept for compatibility, use Gun's Magazine system for new weapons)
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons"))
bool CycleAmmo = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (EditCondition = "CycleAmmo", ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons"))
bool CycleAmmoUnlimited = true;
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons"))
TArray<TSubclassOf<class AEBBullet>> Ammo;
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (EditCondition = "CycleAmmo", ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons"))
int CycleAmmoCount;
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (EditCondition = "CycleAmmo", ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons"))
int CycleAmmoPos;
UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") TSubclassOf<class AEBBullet> ChamberedBullet;
UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") bool Shooting;
@@ -94,12 +138,23 @@ public:
FRandomStream RandomStream;
// Legacy Ammo Functions (kept for compatibility)
UFUNCTION() void NextBullet();
UFUNCTION(BlueprintPure, Category = "Ammo") int GetAmmoCount(bool CountChambered) const;
UFUNCTION(BlueprintPure, Category = "Ammo") TArray<TSubclassOf<class AEBBullet>> GetAmmo(bool CountChambered) const;
UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "Ammo") void SetAmmo(int count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray<TSubclassOf<class AEBBullet>>& NewAmmo);
UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Ammo") void Charge();
UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Ammo") void UnloadChambered(bool ManualCharge);
UFUNCTION(BlueprintPure, Category = "Legacy Ammo") int GetAmmoCount(bool CountChambered) const;
UFUNCTION(BlueprintPure, Category = "Legacy Ammo") TArray<TSubclassOf<class AEBBullet>> GetAmmo(bool CountChambered) const;
UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "Legacy Ammo") void SetAmmo(int count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray<TSubclassOf<class AEBBullet>>& NewAmmo);
UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Legacy Ammo") void Charge();
UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Legacy Ammo") void UnloadChambered(bool ManualCharge);
// New Gun-Integrated Ammo Functions
UFUNCTION(BlueprintPure, Category = "Gun Integration")
int GetMagazineRoundCount() const;
UFUNCTION(BlueprintPure, Category = "Gun Integration")
bool HasChamberedRound() const;
UFUNCTION(BlueprintPure, Category = "Gun Integration")
bool CanFireFromGun() const;
UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Shooting") void SwitchFireMode(EFireMode NewFireMode);
UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Shooting") void GatlingSpool(bool Spool);
UFUNCTION(BlueprintCallable, Category = "Shooting") void Shoot(bool Trigger);
+250
View File
@@ -0,0 +1,250 @@
// Copyright 2016 Mookie. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/StaticMeshComponent.h"
#include "EBBarrel.h"
#include "EBMagazine.h"
#include "EBWeaponConfiguration.h"
#include "EBBulletProperties.h"
#include "EBGun.generated.h"
UENUM(BlueprintType)
enum class EGunState : uint8
{
GS_Ready UMETA(DisplayName = "Ready"),
GS_Firing UMETA(DisplayName = "Firing"),
GS_Reloading UMETA(DisplayName = "Reloading"),
GS_Jammed UMETA(DisplayName = "Jammed"),
GS_Empty UMETA(DisplayName = "Empty"),
GS_SafetyOn UMETA(DisplayName = "Safety On"),
GS_Malfunction UMETA(DisplayName = "Malfunction")
};
UCLASS(Blueprintable, BlueprintType)
class EASYBALLISTICS_API AEBGun : public AActor
{
GENERATED_BODY()
public:
AEBGun();
// Core Components
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
USkeletalMeshComponent* GunMesh;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UEBBarrel* Barrel;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UEBMagazine* Magazine;
// Weapon Configuration
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Configuration")
UEBWeaponConfiguration* WeaponConfig;
// Gun State
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_GunState, Category = "Gun State")
EGunState CurrentGunState = EGunState::GS_Ready;
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Gun State")
bool bSafetyOn = false;
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Gun State")
bool bChamberedRound = false;
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Gun State")
bool bHammerCocked = false;
// Firing Interface
UFUNCTION(BlueprintCallable, Category = "Gun|Firing")
void PullTrigger();
UFUNCTION(BlueprintCallable, Category = "Gun|Firing")
void ReleaseTrigger();
UFUNCTION(BlueprintCallable, Category = "Gun|Controls")
void SetSafety(bool bNewSafetyState);
UFUNCTION(BlueprintCallable, Category = "Gun|Controls")
void CockHammer();
UFUNCTION(BlueprintCallable, Category = "Gun|Controls")
void ReleaseHammer();
// Ammunition Management
UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition")
void LoadMagazine(TArray<UEBBulletPropertiesAsset*> BulletTypes);
UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition")
void EjectMagazine();
UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition")
void InsertMagazine(UEBMagazine* NewMagazine);
UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition")
void ChargingHandle();
UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition")
void EjectChamberedRound();
// State Queries
UFUNCTION(BlueprintPure, Category = "Gun|State")
bool CanFire() const;
UFUNCTION(BlueprintPure, Category = "Gun|State")
bool IsLoaded() const;
UFUNCTION(BlueprintPure, Category = "Gun|State")
int32 GetRoundsInMagazine() const;
UFUNCTION(BlueprintPure, Category = "Gun|State")
int32 GetTotalRounds() const;
UFUNCTION(BlueprintPure, Category = "Gun|State")
UEBBulletPropertiesAsset* GetChamberedBulletType() const;
// Configuration
UFUNCTION(BlueprintCallable, Category = "Gun|Configuration")
void ApplyWeaponConfiguration(UEBWeaponConfiguration* NewConfig);
UFUNCTION(BlueprintCallable, Category = "Gun|Configuration")
void SetFireMode(EFireMode NewFireMode);
UFUNCTION(BlueprintPure, Category = "Gun|Configuration")
EFireMode GetFireMode() const;
// Barrel Integration
UFUNCTION(BlueprintPure, Category = "Gun|Barrel")
float GetBarrelLength() const;
UFUNCTION(BlueprintPure, Category = "Gun|Barrel")
float GetEffectiveMuzzleVelocity() const;
UFUNCTION(BlueprintPure, Category = "Gun|Barrel")
float GetBarrelAccuracy() const;
UFUNCTION(BlueprintCallable, Category = "Gun|Barrel")
FVector CalculateRecoilImpulse() const;
UFUNCTION(BlueprintPure, Category = "Gun|Barrel")
bool IsBarrelConnected() const;
// Events
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGunStateChanged, EGunState, NewState);
UPROPERTY(BlueprintAssignable, Category = "Gun Events")
FOnGunStateChanged OnGunStateChanged;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGunFired, UEBBulletPropertiesAsset*, BulletType);
UPROPERTY(BlueprintAssignable, Category = "Gun Events")
FOnGunFired OnGunFired;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnMagazineEmpty);
UPROPERTY(BlueprintAssignable, Category = "Gun Events")
FOnMagazineEmpty OnMagazineEmpty;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMagazineChanged, UEBMagazine*, NewMagazine);
UPROPERTY(BlueprintAssignable, Category = "Gun Events")
FOnMagazineChanged OnMagazineChanged;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSafetyChanged, bool, bNewSafetyState);
UPROPERTY(BlueprintAssignable, Category = "Gun Events")
FOnSafetyChanged OnSafetyChanged;
// Networking
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
// Replication
UFUNCTION()
void OnRep_GunState();
// Internal Functions
UFUNCTION(BlueprintImplementableEvent, Category = "Gun|Internal")
void OnGunStateChangedInternal(EGunState OldState, EGunState NewState);
UFUNCTION(BlueprintImplementableEvent, Category = "Gun|Internal")
void OnFireAnimationStart();
UFUNCTION(BlueprintImplementableEvent, Category = "Gun|Internal")
void OnReloadAnimationStart();
UFUNCTION(BlueprintImplementableEvent, Category = "Gun|Internal")
void OnJamAnimation();
void SetGunState(EGunState NewState);
void ProcessFiring();
void ProcessReloading();
void CycleAction();
bool FeedNextRound();
// Timers
FTimerHandle FireRateTimer;
FTimerHandle ReloadTimer;
FTimerHandle CycleTimer;
// Firing Control
bool bTriggerPressed = false;
bool bCanProcessNextShot = true;
int32 BurstShotsFired = 0;
// Chambered Round
UPROPERTY(Replicated)
UEBBulletPropertiesAsset* ChamberedBulletType;
private:
EGunState PreviousGunState;
// Server Functions
UFUNCTION(Server, Reliable)
void ServerPullTrigger();
UFUNCTION(Server, Reliable)
void ServerReleaseTrigger();
UFUNCTION(Server, Reliable)
void ServerSetSafety(bool bNewSafetyState);
UFUNCTION(Server, Reliable)
void ServerEjectMagazine();
UFUNCTION(Server, Reliable)
void ServerInsertMagazine(UEBMagazine* NewMagazine);
UFUNCTION(Server, Reliable)
void ServerChargingHandle();
UFUNCTION(Server, Reliable)
void ServerSetFireMode(EFireMode NewFireMode);
// Multicast Functions
UFUNCTION(NetMulticast, Reliable)
void MulticastOnGunFired(UEBBulletPropertiesAsset* BulletType);
UFUNCTION(NetMulticast, Reliable)
void MulticastOnMagazineChanged(UEBMagazine* NewMagazine);
UFUNCTION(NetMulticast, Reliable)
void MulticastOnSafetyChanged(bool bNewSafetyState);
// Event Handlers
UFUNCTION()
void OnMagazineEmptyHandler(UEBMagazine* EmptyMagazine);
UFUNCTION()
void OnBulletFedHandler(UEBMagazine* SourceMagazine, UEBBulletPropertiesAsset* BulletType);
UFUNCTION()
void OnBarrelShotFiredHandler();
// Internal firing function
void FireShot();
};
+229
View File
@@ -0,0 +1,229 @@
// Copyright 2016 Mookie. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "EBBulletProperties.h"
#include "EBMagazine.generated.h"
UENUM(BlueprintType)
enum class EMagazineType : uint8
{
MT_Standard UMETA(DisplayName = "Standard Box Magazine"),
MT_Drum UMETA(DisplayName = "Drum Magazine"),
MT_Tube UMETA(DisplayName = "Tube Magazine"),
MT_Clip UMETA(DisplayName = "Stripper Clip"),
MT_Belt UMETA(DisplayName = "Belt Fed"),
MT_Cylinder UMETA(DisplayName = "Revolver Cylinder"),
MT_Internal UMETA(DisplayName = "Internal Magazine")
};
USTRUCT(BlueprintType)
struct FMagazineConfiguration
{
GENERATED_USTRUCT_BODY()
// Magazine Properties
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config")
EMagazineType MagazineType = EMagazineType::MT_Standard;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config")
int32 MaxCapacity = 30;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config")
bool bAllowMixedBulletTypes = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config")
bool bLastRoundBoltHoldOpen = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config")
float FeedRate = 0.1f; // Seconds between rounds for belt-fed
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config")
float ReloadTime = 2.5f; // Full reload time in seconds
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config")
float MalfunctionChance = 0.001f; // Chance of malfunction per round
FMagazineConfiguration()
{
MagazineType = EMagazineType::MT_Standard;
MaxCapacity = 30;
bAllowMixedBulletTypes = true;
bLastRoundBoltHoldOpen = true;
FeedRate = 0.1f;
ReloadTime = 2.5f;
MalfunctionChance = 0.001f;
}
};
UCLASS(Blueprintable, ClassGroup=(Ballistics), meta=(BlueprintSpawnableComponent))
class EASYBALLISTICS_API UEBMagazine : public UActorComponent
{
GENERATED_BODY()
public:
UEBMagazine();
// Magazine Configuration
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine")
FMagazineConfiguration MagazineConfig;
// Ammunition Storage
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Ammunition")
TArray<UEBBulletPropertiesAsset*> StoredBullets;
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Ammunition")
int32 CurrentCapacity = 0;
// Magazine State
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Magazine State")
bool bIsEmpty = true;
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Magazine State")
bool bIsFull = false;
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Magazine State")
bool bIsJammed = false;
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Magazine State")
bool bBoltHeldOpen = false;
// Ammunition Management
UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition")
void LoadBullets(TArray<UEBBulletPropertiesAsset*> BulletTypes);
UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition")
void LoadSingleBullet(UEBBulletPropertiesAsset* BulletType);
UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition")
UEBBulletPropertiesAsset* FeedNextBullet();
UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition")
void UnloadAllBullets();
UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition")
UEBBulletPropertiesAsset* UnloadLastBullet();
// Magazine Operations
UFUNCTION(BlueprintCallable, Category = "Magazine|Operations")
void ClearJam();
UFUNCTION(BlueprintCallable, Category = "Magazine|Operations")
void ReleaseBoltHold();
UFUNCTION(BlueprintCallable, Category = "Magazine|Operations")
void ForceJam();
// Information
UFUNCTION(BlueprintPure, Category = "Magazine|Info")
int32 GetRemainingCapacity() const;
UFUNCTION(BlueprintPure, Category = "Magazine|Info")
int32 GetCurrentRoundCount() const;
UFUNCTION(BlueprintPure, Category = "Magazine|Info")
int32 GetMaxCapacity() const;
UFUNCTION(BlueprintPure, Category = "Magazine|Info")
float GetFillPercentage() const;
UFUNCTION(BlueprintPure, Category = "Magazine|Info")
UEBBulletPropertiesAsset* PeekNextBullet() const;
UFUNCTION(BlueprintPure, Category = "Magazine|Info")
bool CanFeed() const;
UFUNCTION(BlueprintPure, Category = "Magazine|Info")
bool NeedsReload() const;
UFUNCTION(BlueprintPure, Category = "Magazine|Info")
TArray<UEBBulletPropertiesAsset*> GetBulletTypesSummary() const;
// Events
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMagazineEmpty, UEBMagazine*, Magazine);
UPROPERTY(BlueprintAssignable, Category = "Magazine Events")
FOnMagazineEmpty OnMagazineEmpty;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMagazineFull, UEBMagazine*, Magazine);
UPROPERTY(BlueprintAssignable, Category = "Magazine Events")
FOnMagazineFull OnMagazineFull;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnBulletFed, UEBMagazine*, Magazine, UEBBulletPropertiesAsset*, BulletType);
UPROPERTY(BlueprintAssignable, Category = "Magazine Events")
FOnBulletFed OnBulletFed;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnBulletLoaded, UEBMagazine*, Magazine, UEBBulletPropertiesAsset*, BulletType);
UPROPERTY(BlueprintAssignable, Category = "Magazine Events")
FOnBulletLoaded OnBulletLoaded;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMagazineJammed, UEBMagazine*, Magazine);
UPROPERTY(BlueprintAssignable, Category = "Magazine Events")
FOnMagazineJammed OnMagazineJammed;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnBoltHoldOpen, UEBMagazine*, Magazine);
UPROPERTY(BlueprintAssignable, Category = "Magazine Events")
FOnBoltHoldOpen OnBoltHoldOpen;
// Networking
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
virtual void BeginPlay() override;
// Internal Functions
void UpdateMagazineState();
bool CheckForMalfunction();
void ProcessBoltHoldOpen();
// Replication
UFUNCTION()
void OnRep_StoredBullets();
UFUNCTION()
void OnRep_CurrentCapacity();
UFUNCTION()
void OnRep_IsEmpty();
UFUNCTION()
void OnRep_IsFull();
UFUNCTION()
void OnRep_IsJammed();
UFUNCTION()
void OnRep_BoltHeldOpen();
private:
// Server Functions
UFUNCTION(Server, Reliable)
void ServerLoadBullets(const TArray<UEBBulletPropertiesAsset*>& BulletTypes);
UFUNCTION(Server, Reliable)
void ServerLoadSingleBullet(UEBBulletPropertiesAsset* BulletType);
UFUNCTION(Server, Reliable)
void ServerUnloadAllBullets();
UFUNCTION(Server, Reliable)
void ServerClearJam();
UFUNCTION(Server, Reliable)
void ServerReleaseBoltHold();
// Multicast Functions
UFUNCTION(NetMulticast, Reliable)
void MulticastOnBulletFed(UEBBulletPropertiesAsset* BulletType);
UFUNCTION(NetMulticast, Reliable)
void MulticastOnBulletLoaded(UEBBulletPropertiesAsset* BulletType);
UFUNCTION(NetMulticast, Reliable)
void MulticastOnMagazineJammed();
UFUNCTION(NetMulticast, Reliable)
void MulticastOnBoltHoldOpen();
};
@@ -0,0 +1,310 @@
// Copyright 2016 Mookie. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "EBBulletProperties.h"
#include "EBMaterialResponseMap.h"
#include "EBMagazine.h"
#include "EBWeaponConfiguration.generated.h"
UENUM(BlueprintType)
enum class EFireMode : uint8
{
FM_Auto UMETA(DisplayName = "Full Auto"),
FM_Semiauto UMETA(DisplayName = "Semiauto"),
FM_Burst UMETA(DisplayName = "Burst"),
FM_InterBurst UMETA(DisplayName = "Interruptible Burst"),
FM_Manual UMETA(DisplayName = "Manual"),
FM_Slamfire UMETA(DisplayName = "Slam Fire"),
FM_Gatling UMETA(DisplayName = "Gatling")
};
USTRUCT(BlueprintType)
struct FBarrelConfiguration
{
GENERATED_USTRUCT_BODY()
// Barrel Properties
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Barrel")
float BarrelLength = 16.0f; // inches
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Barrel")
float RiflingTwist = 7.0f; // 1:X twist rate
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Barrel")
float BoreRadius = 0.112f; // inches
// Muzzle Velocity
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
float MuzzleVelocityMin = 91440.0f; // cm/s (~3000 fps)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
float MuzzleVelocityMax = 91440.0f; // cm/s (~3000 fps)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
float MuzzleVelocityVariation = 0.02f; // +/- 2% variation
// Accuracy
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Accuracy")
float InherentAccuracy = 0.001f; // MOA in radians
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Accuracy")
float SpreadMin = 0.0f; // radians
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Accuracy")
float SpreadMax = 0.001f; // radians
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Accuracy")
float SpreadBias = 0.0f;
// Recoil
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil")
float RecoilMultiplier = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil")
FVector RecoilDirection = FVector(0, 0, 1);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil")
float RecoilRecoveryTime = 0.2f;
FBarrelConfiguration()
{
BarrelLength = 16.0f;
RiflingTwist = 7.0f;
BoreRadius = 0.112f;
MuzzleVelocityMin = 91440.0f;
MuzzleVelocityMax = 91440.0f;
MuzzleVelocityVariation = 0.02f;
InherentAccuracy = 0.001f;
SpreadMin = 0.0f;
SpreadMax = 0.001f;
SpreadBias = 0.0f;
RecoilMultiplier = 1.0f;
RecoilDirection = FVector(0, 0, 1);
RecoilRecoveryTime = 0.2f;
}
};
USTRUCT(BlueprintType)
struct FFireControlConfiguration
{
GENERATED_USTRUCT_BODY()
// Fire Modes
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Control")
TArray<EFireMode> AvailableFireModes;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Control")
EFireMode DefaultFireMode = EFireMode::FM_Semiauto;
// Fire Rate
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Rate")
float FireRateMin = 600.0f; // RPM
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Rate")
float FireRateMax = 650.0f; // RPM
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Rate")
float CyclicRateVariation = 0.05f; // +/- 5% variation
// Burst Fire
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Burst Fire")
int32 BurstCount = 3;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Burst Fire")
float BurstCooldown = 0.1f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Burst Fire")
bool bInterruptibleBurst = false;
// Gatling Properties
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gatling")
float GatlingSpoolUpTime = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gatling")
float GatlingSpoolDownTime = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gatling")
bool bGatlingAutoSpool = true;
// Safety
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Safety")
bool bHasSafety = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Safety")
bool bDefaultSafetyOn = false;
// Trigger
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger")
float TriggerWeight = 5.5f; // pounds
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger")
float TriggerTravel = 0.25f; // inches
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger")
bool bTwoStage = false;
FFireControlConfiguration()
{
AvailableFireModes.Add(EFireMode::FM_Semiauto);
DefaultFireMode = EFireMode::FM_Semiauto;
FireRateMin = 600.0f;
FireRateMax = 650.0f;
CyclicRateVariation = 0.05f;
BurstCount = 3;
BurstCooldown = 0.1f;
bInterruptibleBurst = false;
GatlingSpoolUpTime = 1.0f;
GatlingSpoolDownTime = 1.0f;
bGatlingAutoSpool = true;
bHasSafety = true;
bDefaultSafetyOn = false;
TriggerWeight = 5.5f;
TriggerTravel = 0.25f;
bTwoStage = false;
}
};
USTRUCT(BlueprintType)
struct FReliabilityConfiguration
{
GENERATED_USTRUCT_BODY()
// Reliability
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability")
float MalfunctionRate = 0.0001f; // Per round fired
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability")
float JamClearTime = 2.0f; // seconds
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability")
float DirtSensitivity = 0.1f; // 0-1 scale
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability")
float WearFactor = 0.0001f; // Per round fired
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability")
int32 ServiceLife = 10000; // Rounds before overhaul
// Environmental
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environmental")
float TemperatureSensitivity = 0.001f; // Per degree C
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environmental")
float MinOperatingTemp = -40.0f; // Celsius
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environmental")
float MaxOperatingTemp = 60.0f; // Celsius
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environmental")
bool bWaterResistant = true;
FReliabilityConfiguration()
{
MalfunctionRate = 0.0001f;
JamClearTime = 2.0f;
DirtSensitivity = 0.1f;
WearFactor = 0.0001f;
ServiceLife = 10000;
TemperatureSensitivity = 0.001f;
MinOperatingTemp = -40.0f;
MaxOperatingTemp = 60.0f;
bWaterResistant = true;
}
};
UCLASS(BlueprintType)
class EASYBALLISTICS_API UEBWeaponConfiguration : public UDataAsset
{
GENERATED_BODY()
public:
// Weapon Identity
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info")
FString WeaponName = "M4 Carbine";
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info")
FString Manufacturer = "Colt";
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info")
FString Model = "M4A1";
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info")
FString Caliber = "5.56x45mm NATO";
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info")
FString Description = "Standard issue carbine";
// Configuration Sections
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Barrel Configuration")
FBarrelConfiguration BarrelConfig;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Control Configuration")
FFireControlConfiguration FireControlConfig;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability Configuration")
FReliabilityConfiguration ReliabilityConfig;
// Default Ammunition
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ammunition")
UEBBulletPropertiesAsset* DefaultBulletType;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ammunition")
TArray<UEBBulletPropertiesAsset*> CompatibleBulletTypes;
// Magazine Configuration
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine")
int32 DefaultMagazineCapacity = 30;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine")
EMagazineType DefaultMagazineType = EMagazineType::MT_Standard;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine")
bool bAllowMixedAmmo = true;
// Material Response
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
UEBMaterialResponseMap* MaterialResponseMap;
// Physics Settings
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics")
bool bUseMathematicalPhysics = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics")
bool bUseNewImpactSystem = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics")
bool bEnableSpalling = true;
// Networking
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Networking")
bool bReplicateVariables = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Networking")
bool bReplicateShotEvents = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Networking")
bool bClientSideAim = false;
// Utility Functions
UFUNCTION(BlueprintPure, Category = "Weapon Configuration")
bool IsFireModeAvailable(EFireMode FireMode) const;
UFUNCTION(BlueprintPure, Category = "Weapon Configuration")
bool IsBulletTypeCompatible(UEBBulletPropertiesAsset* BulletType) const;
UFUNCTION(BlueprintPure, Category = "Weapon Configuration")
float GetEffectiveFireRate(EFireMode FireMode) const;
UFUNCTION(BlueprintPure, Category = "Weapon Configuration")
FString GetWeaponDisplayName() const;
UFUNCTION(BlueprintPure, Category = "Weapon Configuration")
FString GetFullWeaponName() const;
UFUNCTION(BlueprintCallable, Category = "Weapon Configuration")
void ApplyConfigurationToGun(class AEBGun* Gun) const;
};
@@ -16,7 +16,12 @@ public class EasyBallisticsEditor : ModuleRules
"AssetTools",
"EditorWidgets",
"EasyBallistics",
"PhysicsCore"
"PhysicsCore",
"ComponentVisualizers",
"EditorStyle",
"EditorSubsystem",
"LevelEditor",
"SceneOutliner"
});
PrivateDependencyModuleNames.AddRange(new string[] {
@@ -26,7 +31,21 @@ public class EasyBallisticsEditor : ModuleRules
"WorkspaceMenuStructure",
"DesktopPlatform",
"ToolMenus",
"Json"
"Json",
"DetailCustomizations",
"ActorPickerMode",
"SceneDepthPickerMode",
"ViewportInteraction",
"MeshPaint",
"ContentBrowser",
"AssetRegistry",
"EditorScriptingUtilities",
"EditorInteractiveToolsFramework",
"InteractiveToolsFramework",
"EngineSettings",
"RenderCore",
"RHI",
"Projects"
});
}
}
@@ -2,50 +2,130 @@
#include "EBBarrelComponentFactory.h"
#include "EBBarrel.h"
#include "AssetToolsModule.h"
#include "Engine/Selection.h"
#include "Components/SceneComponent.h"
#include "DrawDebugHelpers.h"
#define LOCTEXT_NAMESPACE "EBBarrelComponentFactory"
UClass* FEBBarrelComponentFactory::GetSupportedClass() const
void FEBBarrelComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
return UEBBarrel::StaticClass();
}
uint32 FEBBarrelComponentFactory::GetCategories()
{
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics"));
}
FText FEBBarrelComponentFactory::GetAssetDescription(const FAssetData& AssetData) const
{
return LOCTEXT("EBBarrelComponentDescription", "A weapon barrel component that handles firing mechanics, ammunition management, and ballistic calculations for projectile weapons.");
}
UClass* FEBBarrelComponentAssetBroker::GetSupportedAssetClass()
{
return UEBBarrel::StaticClass();
}
bool FEBBarrelComponentAssetBroker::AssignAssetToComponent(UActorComponent* InComponent, UObject* InAsset)
{
if (UEBBarrel* BarrelComponent = Cast<UEBBarrel>(InComponent))
const UEBBarrel* BarrelComponent = Cast<const UEBBarrel>(Component);
if (!BarrelComponent)
{
// Components don't typically have assets assigned to them directly
// This would be used if we had barrel configuration assets
return false;
return;
}
// Get the component's transform
FTransform ComponentTransform = BarrelComponent->GetComponentTransform();
FVector Location = ComponentTransform.GetLocation();
FVector Forward = ComponentTransform.GetUnitAxis(EAxis::X);
FVector Right = ComponentTransform.GetUnitAxis(EAxis::Y);
FVector Up = ComponentTransform.GetUnitAxis(EAxis::Z);
// Draw barrel representation
FColor BarrelColor = FColor::Blue;
if (BarrelComponent->ChamberedBullet)
{
BarrelColor = FColor::Green; // Loaded
}
else if (BarrelComponent->Shooting)
{
BarrelColor = FColor::Red; // Firing
}
// Draw barrel cylinder
float BarrelLength = FMath::Max(BarrelComponent->BarrelLength, 10.0f); // Minimum visual length
float BarrelRadius = 2.0f;
FVector BarrelEnd = Location + (Forward * BarrelLength);
// Draw barrel outline
DrawWireCylinder(PDI, Location, Forward, Right, Up, BarrelColor, BarrelRadius, BarrelLength, 12, SDPG_Foreground);
// Draw muzzle
FVector MuzzleLocation = BarrelEnd;
DrawWireBox(PDI, FBox(MuzzleLocation - FVector(2, 2, 2), MuzzleLocation + FVector(2, 2, 2)), FColor::Orange, SDPG_Foreground);
// Draw firing direction indicator
if (BarrelComponent->Shooting)
{
FVector FireDirection = BarrelEnd + (Forward * 20.0f);
PDI->DrawLine(BarrelEnd, FireDirection, FColor::Red, SDPG_Foreground, 2.0f);
}
// Draw spread cone when aiming
if (BarrelComponent->Spread > 0.0f)
{
float SpreadAngle = BarrelComponent->Spread;
float ConeLength = 50.0f;
FVector ConeEnd = BarrelEnd + (Forward * ConeLength);
float ConeRadius = FMath::Tan(SpreadAngle) * ConeLength;
// Draw spread cone outline (temporarily commented out due to function signature issues)
// FQuat ConeRotation = FQuat::FindBetweenVectors(FVector::ForwardVector, Forward);
// DrawWireCone(PDI, FTransform(ConeRotation, BarrelEnd), ConeLength, ConeRadius, 8, FColor::Yellow);
// Alternative: Draw lines to represent the cone
int32 NumSides = 8;
for (int32 i = 0; i < NumSides; i++)
{
float Angle = (2.0f * PI * i) / NumSides;
FVector RadialDirection = Right * FMath::Cos(Angle) + Up * FMath::Sin(Angle);
FVector ConePoint = BarrelEnd + Forward * ConeLength + RadialDirection * ConeRadius;
PDI->DrawLine(BarrelEnd, ConePoint, FColor::Yellow, SDPG_Foreground);
}
}
// Draw ammo count indicator
if (BarrelComponent->Ammo.Num() > 0)
{
FVector AmmoIndicatorLocation = Location + (Right * 10.0f);
for (int32 i = 0; i < FMath::Min(BarrelComponent->Ammo.Num(), 10); i++)
{
FVector AmmoLocation = AmmoIndicatorLocation + (Up * i * 3.0f);
FVector BoxExtent = FVector(0.5f, 0.5f, 1.0f);
DrawWireBox(PDI, FBox(AmmoLocation - BoxExtent, AmmoLocation + BoxExtent), FColor::Cyan, SDPG_Foreground);
}
}
}
bool FEBBarrelComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
return false;
}
UObject* FEBBarrelComponentAssetBroker::GetAssetFromComponent(UActorComponent* InComponent)
bool FEBBarrelComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const
{
return false;
}
bool FEBBarrelComponentVisualizer::GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const
{
return false;
}
bool FEBBarrelComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale)
{
return false;
}
bool FEBBarrelComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
return false;
}
TSharedPtr<SWidget> FEBBarrelComponentVisualizer::GenerateContextMenu() const
{
return TSharedPtr<SWidget>();
}
bool FEBBarrelComponentVisualizer::IsVisualizationShown(const UActorComponent* Component) const
{
return true;
}
void FEBBarrelComponentVisualizer::EndEditing()
{
if (UEBBarrel* BarrelComponent = Cast<UEBBarrel>(InComponent))
{
// Return any associated asset if we had barrel configuration assets
return nullptr;
}
return nullptr;
}
#undef LOCTEXT_NAMESPACE
@@ -0,0 +1,44 @@
// Copyright 2016 Mookie. All Rights Reserved.
#include "EBGunActorFactory.h"
#include "EBGun.h"
#include "Engine/Selection.h"
#include "Editor.h"
UEBGunActorFactory::UEBGunActorFactory()
{
DisplayName = NSLOCTEXT("EasyBallistics", "GunDisplayName", "EasyBallistics Gun");
NewActorClass = AEBGun::StaticClass();
bUsePlacementExtent = true;
}
bool UEBGunActorFactory::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg)
{
if (AssetData.IsValid())
{
UClass* AssetClass = AssetData.GetClass();
if (AssetClass && AssetClass->IsChildOf(UEBWeaponConfiguration::StaticClass()))
{
return true;
}
}
return Super::CanCreateActorFrom(AssetData, OutErrorMsg);
}
AActor* UEBGunActorFactory::SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams)
{
AEBGun* NewGun = Cast<AEBGun>(Super::SpawnActor(InAsset, InLevel, InTransform, InSpawnParams));
if (NewGun)
{
// If spawned from a weapon configuration asset, apply it
if (UEBWeaponConfiguration* WeaponConfig = Cast<UEBWeaponConfiguration>(InAsset))
{
NewGun->WeaponConfig = WeaponConfig;
NewGun->ApplyWeaponConfiguration(WeaponConfig);
}
}
return NewGun;
}
@@ -0,0 +1,100 @@
// Copyright 2016 Mookie. All Rights Reserved.
#include "EBMagazineComponentFactory.h"
#include "EBMagazine.h"
#include "Engine/Selection.h"
#include "Components/SceneComponent.h"
#include "DrawDebugHelpers.h"
#define LOCTEXT_NAMESPACE "EBMagazineComponentFactory"
void FEBMagazineComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
const UEBMagazine* MagazineComponent = Cast<const UEBMagazine>(Component);
if (!MagazineComponent)
{
return;
}
// Get the component's owner actor
const AActor* Owner = MagazineComponent->GetOwner();
if (!Owner)
{
return;
}
// Draw a simple visualization for the magazine
FVector Location = Owner->GetActorLocation();
FVector Offset = FVector(0, 0, -20); // Offset below the actor
FVector MagazineLocation = Location + Offset;
// Draw magazine representation
FColor MagazineColor = FColor::Yellow;
if (MagazineComponent->bIsEmpty)
{
MagazineColor = FColor::Red;
}
else if (MagazineComponent->bIsFull)
{
MagazineColor = FColor::Green;
}
// Draw a box to represent the magazine
FVector BoxExtent = FVector(2, 8, 15);
DrawWireBox(PDI, FBox(MagazineLocation - BoxExtent, MagazineLocation + BoxExtent), MagazineColor, SDPG_Foreground);
// Draw capacity indicator
float FillRatio = MagazineComponent->GetFillPercentage();
FVector FillLocation = MagazineLocation + FVector(0, 0, -BoxExtent.Z + (BoxExtent.Z * 2 * FillRatio));
FVector FillExtent = FVector(1, 6, BoxExtent.Z * FillRatio);
DrawWireBox(PDI, FBox(FillLocation - FillExtent, FillLocation + FillExtent), FColor::Orange, SDPG_Foreground);
// Draw text showing round count
FString RoundCountText = FString::Printf(TEXT("%d/%d"),
MagazineComponent->GetCurrentRoundCount(),
MagazineComponent->GetMaxCapacity());
// Note: Text drawing in visualizers requires more complex setup
// For now, we'll just draw the geometric representation
}
bool FEBMagazineComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
return false;
}
bool FEBMagazineComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const
{
return false;
}
bool FEBMagazineComponentVisualizer::GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const
{
return false;
}
bool FEBMagazineComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale)
{
return false;
}
bool FEBMagazineComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
return false;
}
TSharedPtr<SWidget> FEBMagazineComponentVisualizer::GenerateContextMenu() const
{
return TSharedPtr<SWidget>();
}
bool FEBMagazineComponentVisualizer::IsVisualizationShown(const UActorComponent* Component) const
{
return true;
}
void FEBMagazineComponentVisualizer::EndEditing()
{
}
#undef LOCTEXT_NAMESPACE
@@ -3,9 +3,23 @@
#include "EBMathematicalBallisticsFactory.h"
#include "EBMathematicalBallistics.h"
#include "AssetToolsModule.h"
#include "EasyBallisticsEditor.h"
#define LOCTEXT_NAMESPACE "EBMathematicalBallisticsFactory"
UEBMathematicalBallisticsFactory::UEBMathematicalBallisticsFactory(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SupportedClass = UEBMathematicalBallistics::StaticClass();
bCreateNew = true;
bEditAfterNew = true;
}
UObject* UEBMathematicalBallisticsFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
return NewObject<UEBMathematicalBallistics>(InParent, Class, Name, Flags);
}
UClass* FEBMathematicalBallisticsFactory::GetSupportedClass() const
{
return UEBMathematicalBallistics::StaticClass();
@@ -13,8 +27,7 @@ UClass* FEBMathematicalBallisticsFactory::GetSupportedClass() const
uint32 FEBMathematicalBallisticsFactory::GetCategories()
{
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics"));
return FEasyBallisticsEditorModule::GetBallisticsAssetCategory();
}
FText FEBMathematicalBallisticsFactory::GetAssetDescription(const FAssetData& AssetData) const
@@ -0,0 +1,136 @@
// Copyright 2016 Mookie. All Rights Reserved.
#include "EBWeaponConfigurationFactory.h"
#include "EBWeaponConfiguration.h"
#include "EBMagazine.h"
#include "AssetToolsModule.h"
#include "EasyBallisticsEditor.h"
#define LOCTEXT_NAMESPACE "EBWeaponConfigurationFactory"
UEBWeaponConfigurationFactory::UEBWeaponConfigurationFactory()
{
bCreateNew = true;
bEditAfterNew = true;
SupportedClass = UEBWeaponConfiguration::StaticClass();
}
UObject* UEBWeaponConfigurationFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
UEBWeaponConfiguration* WeaponConfig = NewObject<UEBWeaponConfiguration>(InParent, Class, Name, Flags);
if (WeaponConfig)
{
// Set up default configuration based on name
FString AssetName = Name.ToString();
if (AssetName.Contains(TEXT("M4")) || AssetName.Contains(TEXT("AR15")))
{
// Configure for M4/AR-15 type weapon
WeaponConfig->WeaponName = TEXT("M4 Carbine");
WeaponConfig->Manufacturer = TEXT("Colt");
WeaponConfig->Model = TEXT("M4A1");
WeaponConfig->Caliber = TEXT("5.56x45mm NATO");
WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Semiauto);
WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Auto);
WeaponConfig->FireControlConfig.FireRateMin = 700.0f;
WeaponConfig->FireControlConfig.FireRateMax = 950.0f;
WeaponConfig->BarrelConfig.BarrelLength = 14.5f;
WeaponConfig->DefaultMagazineCapacity = 30;
}
else if (AssetName.Contains(TEXT("AK")) || AssetName.Contains(TEXT("74")))
{
// Configure for AK type weapon
WeaponConfig->WeaponName = TEXT("AK-74");
WeaponConfig->Manufacturer = TEXT("Kalashnikov");
WeaponConfig->Model = TEXT("AK-74M");
WeaponConfig->Caliber = TEXT("5.45x39mm");
WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Semiauto);
WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Auto);
WeaponConfig->FireControlConfig.FireRateMin = 650.0f;
WeaponConfig->FireControlConfig.FireRateMax = 750.0f;
WeaponConfig->BarrelConfig.BarrelLength = 16.3f;
WeaponConfig->DefaultMagazineCapacity = 30;
}
else if (AssetName.Contains(TEXT("Pistol")) || AssetName.Contains(TEXT("Glock")) || AssetName.Contains(TEXT("1911")))
{
// Configure for pistol
WeaponConfig->WeaponName = TEXT("M1911");
WeaponConfig->Manufacturer = TEXT("Colt");
WeaponConfig->Model = TEXT("M1911A1");
WeaponConfig->Caliber = TEXT(".45 ACP");
WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Semiauto);
WeaponConfig->FireControlConfig.DefaultFireMode = EFireMode::FM_Semiauto;
WeaponConfig->FireControlConfig.FireRateMin = 300.0f;
WeaponConfig->FireControlConfig.FireRateMax = 400.0f;
WeaponConfig->BarrelConfig.BarrelLength = 5.0f;
WeaponConfig->DefaultMagazineCapacity = 7;
WeaponConfig->DefaultMagazineType = EMagazineType::MT_Standard;
}
else if (AssetName.Contains(TEXT("Sniper")) || AssetName.Contains(TEXT("Bolt")))
{
// Configure for bolt-action sniper rifle
WeaponConfig->WeaponName = TEXT("M24 SWS");
WeaponConfig->Manufacturer = TEXT("Remington");
WeaponConfig->Model = TEXT("M24");
WeaponConfig->Caliber = TEXT("7.62x51mm NATO");
WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Manual);
WeaponConfig->FireControlConfig.DefaultFireMode = EFireMode::FM_Manual;
WeaponConfig->FireControlConfig.FireRateMin = 60.0f;
WeaponConfig->FireControlConfig.FireRateMax = 60.0f;
WeaponConfig->BarrelConfig.BarrelLength = 24.0f;
WeaponConfig->BarrelConfig.InherentAccuracy = 0.0003f; // Very accurate
WeaponConfig->DefaultMagazineCapacity = 5;
WeaponConfig->DefaultMagazineType = EMagazineType::MT_Internal;
}
else if (AssetName.Contains(TEXT("Shotgun")))
{
// Configure for shotgun
WeaponConfig->WeaponName = TEXT("M870");
WeaponConfig->Manufacturer = TEXT("Remington");
WeaponConfig->Model = TEXT("870");
WeaponConfig->Caliber = TEXT("12 Gauge");
WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Manual);
WeaponConfig->FireControlConfig.DefaultFireMode = EFireMode::FM_Manual;
WeaponConfig->FireControlConfig.FireRateMin = 120.0f;
WeaponConfig->FireControlConfig.FireRateMax = 120.0f;
WeaponConfig->BarrelConfig.BarrelLength = 18.5f;
WeaponConfig->DefaultMagazineCapacity = 8;
WeaponConfig->DefaultMagazineType = EMagazineType::MT_Tube;
}
else if (AssetName.Contains(TEXT("LMG")) || AssetName.Contains(TEXT("Machine")))
{
// Configure for light machine gun
WeaponConfig->WeaponName = TEXT("M249 SAW");
WeaponConfig->Manufacturer = TEXT("FN Herstal");
WeaponConfig->Model = TEXT("M249");
WeaponConfig->Caliber = TEXT("5.56x45mm NATO");
WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Auto);
WeaponConfig->FireControlConfig.DefaultFireMode = EFireMode::FM_Auto;
WeaponConfig->FireControlConfig.FireRateMin = 750.0f;
WeaponConfig->FireControlConfig.FireRateMax = 1000.0f;
WeaponConfig->BarrelConfig.BarrelLength = 18.0f;
WeaponConfig->DefaultMagazineCapacity = 200;
WeaponConfig->DefaultMagazineType = EMagazineType::MT_Belt;
}
}
return WeaponConfig;
}
UClass* FEBWeaponConfigurationFactory::GetSupportedClass() const
{
return UEBWeaponConfiguration::StaticClass();
}
uint32 FEBWeaponConfigurationFactory::GetCategories()
{
return FEasyBallisticsEditorModule::GetBallisticsAssetCategory();
}
FText FEBWeaponConfigurationFactory::GetAssetDescription(const FAssetData& AssetData) const
{
return LOCTEXT("EBWeaponConfigurationDescription", "Defines weapon properties including fire modes, barrel characteristics, magazine types, and ammunition specifications for ballistic weapons.");
}
#undef LOCTEXT_NAMESPACE
@@ -3,14 +3,32 @@
#include "EasyBallisticsEditor.h"
#include "AssetToolsModule.h"
#include "PropertyEditorModule.h"
#include "EditorModeRegistry.h"
#include "ComponentVisualizer.h"
#include "UnrealEdGlobals.h"
#include "Editor/UnrealEdEngine.h"
// Asset Factories
#include "EBMaterialResponseMapFactory.h"
#include "EBBulletPropertiesFactory.h"
#include "EBBulletActorFactory.h"
#include "EBBarrelComponentFactory.h"
#include "EBMathematicalBallisticsFactory.h"
#include "EBWeaponConfigurationFactory.h"
#include "EBGunActorFactory.h"
// Component Visualizers
#include "EBBarrelComponentFactory.h"
#include "EBMagazineComponentFactory.h"
// Customizations
#include "EBPhysicalMaterialCustomization.h"
#include "EBJsonImportExportTool.h"
// Component Classes
#include "EBBarrel.h"
#include "EBMagazine.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "Editor.h"
#define LOCTEXT_NAMESPACE "FEasyBallisticsEditorModule"
@@ -41,6 +59,90 @@ void FEasyBallisticsEditorModule::StartupModule()
RegisteredAssetTypeActions.Add(BulletPropertiesActions);
}
// Register Material Properties Asset factory
{
TSharedRef<IAssetTypeActions> MaterialPropertiesActions = MakeShareable(new FEBMaterialPropertiesAssetFactory());
AssetTools.RegisterAssetTypeActions(MaterialPropertiesActions);
RegisteredAssetTypeActions.Add(MaterialPropertiesActions);
}
// Register Mathematical Ballistics Asset factory
{
TSharedRef<IAssetTypeActions> MathematicalBallisticsActions = MakeShareable(new FEBMathematicalBallisticsFactory());
AssetTools.RegisterAssetTypeActions(MathematicalBallisticsActions);
RegisteredAssetTypeActions.Add(MathematicalBallisticsActions);
}
// Register Weapon Configuration Asset factory
{
TSharedRef<IAssetTypeActions> WeaponConfigurationActions = MakeShareable(new FEBWeaponConfigurationFactory());
AssetTools.RegisterAssetTypeActions(WeaponConfigurationActions);
RegisteredAssetTypeActions.Add(WeaponConfigurationActions);
}
// Register UFactory objects (these handle the "Add" menu in Content Browser)
// These need to be manually registered to appear in the Add menu
// Register Material Response Map UFactory
if (!GetDefault<UEBMaterialResponseMapFactory>())
{
NewObject<UEBMaterialResponseMapFactory>();
}
// Register Bullet Properties UFactory
if (!GetDefault<UEBBulletPropertiesAssetFactory>())
{
NewObject<UEBBulletPropertiesAssetFactory>();
}
// Register Material Properties UFactory
if (!GetDefault<UEBMaterialPropertiesAssetFactory>())
{
NewObject<UEBMaterialPropertiesAssetFactory>();
}
// Register Mathematical Ballistics UFactory
if (!GetDefault<UEBMathematicalBallisticsFactory>())
{
NewObject<UEBMathematicalBallisticsFactory>();
}
// Register Weapon Configuration UFactory
if (!GetDefault<UEBWeaponConfigurationFactory>())
{
NewObject<UEBWeaponConfigurationFactory>();
}
// Register UObject-based factories for actors
if (GEditor)
{
// Register Weapon Configuration factory
UEBWeaponConfigurationFactory* WeaponConfigFactory = NewObject<UEBWeaponConfigurationFactory>();
// UFactory objects are automatically discovered by the editor
// Register Gun Actor factory
UEBGunActorFactory* GunActorFactory = NewObject<UEBGunActorFactory>();
GEditor->ActorFactories.Add(GunActorFactory);
// Register Bullet Actor factory
UEBBulletActorFactory* BulletActorFactory = NewObject<UEBBulletActorFactory>();
GEditor->ActorFactories.Add(BulletActorFactory);
}
// Register Component Visualizers
if (GUnrealEd)
{
// Register Barrel Component Visualizer
TSharedPtr<FEBBarrelComponentVisualizer> BarrelVisualizer = MakeShareable(new FEBBarrelComponentVisualizer());
GUnrealEd->RegisterComponentVisualizer(UEBBarrel::StaticClass()->GetFName(), BarrelVisualizer);
RegisteredComponentClassNames.Add(UEBBarrel::StaticClass()->GetFName());
// Register Magazine Component Visualizer
TSharedPtr<FEBMagazineComponentVisualizer> MagazineVisualizer = MakeShareable(new FEBMagazineComponentVisualizer());
GUnrealEd->RegisterComponentVisualizer(UEBMagazine::StaticClass()->GetFName(), MagazineVisualizer);
RegisteredComponentClassNames.Add(UEBMagazine::StaticClass()->GetFName());
}
// Register Physical Material customization
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.RegisterCustomClassLayout(
@@ -60,6 +162,16 @@ void FEasyBallisticsEditorModule::ShutdownModule()
// Unregister JSON Import/Export tool
FEBJsonImportExportTool::UnregisterMenus();
// Unregister Component Visualizers
if (GUnrealEd)
{
for (const FName& ClassName : RegisteredComponentClassNames)
{
GUnrealEd->UnregisterComponentVisualizer(ClassName);
}
}
RegisteredComponentClassNames.Empty();
// Unregister Physical Material customization
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
{
@@ -3,25 +3,19 @@
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
#include "ComponentAssetBroker.h"
#include "ComponentVisualizer.h"
#include "EBBarrel.h"
class FEBBarrelComponentFactory : public FAssetTypeActions_Base
class FEBBarrelComponentVisualizer : public FComponentVisualizer
{
public:
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBBarrelComponent", "Barrel Component"); }
virtual FColor GetTypeColor() const override { return FColor(100, 150, 255); }
virtual UClass* GetSupportedClass() const override;
virtual uint32 GetCategories() override;
virtual FText GetAssetDescription(const FAssetData& AssetData) const override;
virtual bool HasActions(const TArray<UObject*>& InObjects) const override { return false; }
};
class FEBBarrelComponentAssetBroker : public IComponentAssetBroker
{
public:
virtual UClass* GetSupportedAssetClass() override;
virtual bool AssignAssetToComponent(UActorComponent* InComponent, UObject* InAsset) override;
virtual UObject* GetAssetFromComponent(UActorComponent* InComponent) override;
virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override;
virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override;
virtual bool GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const override;
virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override;
virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override;
virtual TSharedPtr<SWidget> GenerateContextMenu() const override;
virtual bool IsVisualizationShown(const UActorComponent* Component) const;
virtual void EndEditing() override;
};
@@ -0,0 +1,21 @@
// Copyright 2016 Mookie. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ActorFactories/ActorFactory.h"
#include "EBWeaponConfiguration.h"
#include "EBGunActorFactory.generated.h"
UCLASS()
class EASYBALLISTICSEDITOR_API UEBGunActorFactory : public UActorFactory
{
GENERATED_BODY()
public:
UEBGunActorFactory();
// UActorFactory interface
virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override;
virtual AActor* SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams) override;
};
@@ -0,0 +1,21 @@
// Copyright 2016 Mookie. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ComponentVisualizer.h"
#include "EBMagazine.h"
class FEBMagazineComponentVisualizer : public FComponentVisualizer
{
public:
virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override;
virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override;
virtual bool GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const override;
virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override;
virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override;
virtual TSharedPtr<SWidget> GenerateContextMenu() const override;
virtual bool IsVisualizationShown(const UActorComponent* Component) const;
virtual void EndEditing() override;
};
@@ -4,7 +4,21 @@
#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
#include "Factories/Factory.h"
#include "EBMathematicalBallistics.h"
#include "EBMathematicalBallisticsFactory.generated.h"
UCLASS()
class EASYBALLISTICSEDITOR_API UEBMathematicalBallisticsFactory : public UFactory
{
GENERATED_UCLASS_BODY()
public:
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
virtual bool ShouldShowInNewMenu() const override { return true; }
virtual FText GetDisplayName() const override { return NSLOCTEXT("EBFactory", "MathematicalBallisticsText", "Mathematical Ballistics"); }
virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "MathematicalBallisticsTooltip", "Creates a new Mathematical Ballistics asset for ballistic calculations"); }
};
class FEBMathematicalBallisticsFactory : public FAssetTypeActions_Base
{
@@ -0,0 +1,34 @@
// Copyright 2016 Mookie. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
#include "Factories/Factory.h"
#include "EBWeaponConfiguration.h"
#include "EBWeaponConfigurationFactory.generated.h"
UCLASS()
class EASYBALLISTICSEDITOR_API UEBWeaponConfigurationFactory : public UFactory
{
GENERATED_BODY()
public:
UEBWeaponConfigurationFactory();
// UFactory interface
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
virtual bool ShouldShowInNewMenu() const override { return true; }
virtual FText GetDisplayName() const override { return NSLOCTEXT("EBFactory", "WeaponConfigurationText", "Weapon Configuration"); }
virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "WeaponConfigurationTooltip", "Creates a new Weapon Configuration asset defining weapon properties and behavior"); }
};
class FEBWeaponConfigurationFactory : public FAssetTypeActions_Base
{
public:
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBWeaponConfiguration", "Weapon Configuration"); }
virtual FColor GetTypeColor() const override { return FColor(255, 255, 100); }
virtual UClass* GetSupportedClass() const override;
virtual uint32 GetCategories() override;
virtual FText GetAssetDescription(const FAssetData& AssetData) const override;
};
@@ -4,18 +4,26 @@
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "AssetTypeActions_Base.h"
#include "AssetToolsModule.h"
#include "AssetTypeCategories.h"
#include "IAssetTypeActions.h"
class FEasyBallisticsEditorModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
/** Gets the ballistics asset category */
static EAssetTypeCategories::Type GetBallisticsAssetCategory() { return BallisticsAssetCategory; }
private:
TArray<TSharedPtr<IAssetTypeActions>> RegisteredAssetTypeActions;
/** Handle to the asset category we registered */
static EAssetTypeCategories::Type BallisticsAssetCategory;
/** Array of asset type actions we registered */
TArray<TSharedPtr<IAssetTypeActions>> RegisteredAssetTypeActions;
/** Array of component visualizers we registered */
TArray<FName> RegisteredComponentClassNames;
};
+71
View File
@@ -2,6 +2,39 @@
Understanding the fundamental concepts behind EasyBallistics will help you build more effective and realistic ballistic systems.
## Architectural Approaches
EasyBallistics supports two main architectural approaches to suit different project needs:
### Gun-Centric Architecture (Recommended)
The **Gun-Centric Architecture** uses the `AEBGun` class as a complete weapon system that integrates:
- **Barrel Component**: Firing mechanism and ballistic calculations
- **Magazine Component**: Ammunition storage and feeding
- **Weapon Configuration**: Centralized parameters and settings
- **State Management**: Gun states, safety, fire modes
This approach provides:
-**Complete weapon simulation** with realistic gun mechanics
-**Magazine management** with mixed ammunition types
-**Multiple fire modes** (semi-auto, full-auto, burst, etc.)
-**Safety systems** and weapon state tracking
-**Network-ready** with built-in replication
**Best for**: Complete weapon systems, realistic firearms, tactical shooters
See the **[Gun Blueprint Guide](../tutorials/gun-blueprint-guide.md)** for implementation details.
### Component-Based Architecture
The **Component-Based Architecture** uses individual `UEBBarrel` components for custom implementations:
- **Flexible attachment** to any actor
- **Custom weapon designs** and non-traditional projectile systems
- **Granular control** over each system component
- **Legacy compatibility** with existing projects
**Best for**: Custom weapons, vehicles with weapons, non-firearm projectile systems
See the **[Blueprint Integration Guide](../tutorials/blueprint-integration.md)** for implementation details.
## System Architecture
EasyBallistics uses a modular architecture that separates concerns for maximum flexibility:
@@ -267,3 +300,41 @@ Now that you understand the core concepts:
1. [API Reference](../api/overview) - Detailed function documentation
2. [Quick Start Guide](../getting-started/quick-start) - Create your first weapon
3. [Troubleshooting](../troubleshooting) - Common issues and solutions
## Architectural Approaches
EasyBallistics supports two main architectural approaches to suit different project needs:
### Gun-Centric Architecture (Recommended)
The **Gun-Centric Architecture** uses the `AEBGun` class as a complete weapon system that integrates:
- **Barrel Component**: Firing mechanism and ballistic calculations
- **Magazine Component**: Ammunition storage and feeding
- **Weapon Configuration**: Centralized parameters and settings
- **State Management**: Gun states, safety, fire modes
This approach provides:
-**Complete weapon simulation** with realistic gun mechanics
-**Magazine management** with mixed ammunition types
-**Multiple fire modes** (semi-auto, full-auto, burst, etc.)
-**Safety systems** and weapon state tracking
-**Network-ready** with built-in replication
**Best for**: Complete weapon systems, realistic firearms, tactical shooters
See the **[Gun Blueprint Guide](../tutorials/gun-blueprint-guide.md)** for implementation details.
### Component-Based Architecture
The **Component-Based Architecture** uses individual `UEBBarrel` components for custom implementations:
- **Flexible attachment** to any actor
- **Custom weapon designs** and non-traditional projectile systems
- **Granular control** over each system component
- **Legacy compatibility** with existing projects
**Best for**: Custom weapons, vehicles with weapons, non-firearm projectile systems
See the **[Blueprint Integration Guide](../tutorials/blueprint-integration.md)** for implementation details.
## Core System Components
### Bullet Properties Asset
// ... existing content ...
+4
View File
@@ -2,6 +2,10 @@
Get up and running with EasyBallistics in under 10 minutes! This guide will walk you through creating your first ballistic weapon system.
> **🎯 New Gun-Centric Approach Available!**
>
> This guide shows the **component-based approach** using individual barrel components. For a complete weapon system with magazines, fire modes, and realistic gun mechanics, see the **[Gun Blueprint Creation Guide](../tutorials/gun-blueprint-guide.md)**.
## Overview
In this quick start, you'll:
+3 -2
View File
@@ -66,8 +66,9 @@ graph TD
## Getting Started
1. **[Installation Guide](getting-started/installation)** - Install and configure the plugin
2. **[Quick Start](getting-started/quick-start)** - Create your first weapon in 10 minutes
3. **[Core Concepts](core-concepts/overview)** - Understand the system architecture
2. **[Gun Blueprint Guide](tutorials/gun-blueprint-guide)** - Create complete weapons with the new Gun-Centric Architecture
3. **[Quick Start](getting-started/quick-start)** - Alternative component-based approach
4. **[Core Concepts](core-concepts/overview)** - Understand the system architecture
## Core Systems
@@ -2,6 +2,14 @@
EasyBallistics provides extensive Blueprint support, allowing developers to create sophisticated ballistic systems without writing C++ code. This guide covers everything from basic setup to advanced Blueprint patterns.
> **🎯 New Gun-Centric Architecture Available!**
>
> EasyBallistics now features a comprehensive **Gun-Centric Architecture** with the new `AEBGun` class that provides integrated weapon systems with magazine management, multiple fire modes, and realistic gun mechanics.
>
> **For creating complete weapon systems**, see the **[Gun Blueprint Creation Guide](gun-blueprint-guide.md)** which covers the new architecture.
>
> **This guide** focuses on the component-based approach using individual `UEBBarrel` components, which is still fully supported and useful for custom implementations.
## Prerequisites
- Basic knowledge of Unreal Engine Blueprints
+533
View File
@@ -0,0 +1,533 @@
# Gun Blueprint Creation Guide
This guide covers creating fully functional gun blueprints using EasyBallistics' new **Gun-Centric Architecture**. The `AEBGun` class provides a complete weapon system with integrated barrel, magazine, and ammunition management.
## Prerequisites
- Basic knowledge of Unreal Engine Blueprints
- Understanding of Data Assets and Actor Components
- Familiarity with Unreal's event system
- EasyBallistics plugin installed and enabled
## Architecture Overview
The Gun-Centric Architecture consists of:
- **`AEBGun`**: Main weapon actor with integrated systems
- **`UEBBarrel`**: Firing mechanism and ballistic calculations
- **`UEBMagazine`**: Ammunition storage and feeding
- **`UEBWeaponConfiguration`**: Centralized weapon parameters
- **`UEBBulletPropertiesAsset`**: Ammunition specifications
## Step-by-Step Gun Creation
### 1. Create Required Data Assets
#### A. Create Bullet Properties Asset
1. **Right-click** in Content Browser → **Ballistics****Bullet Properties**
2. **Name**: `BP_556NATO_BulletProperties`
3. **Configure properties**:
```
Basic Properties:
- Grain Weight: 55.0
- Diameter Inches: 0.224
- Length Inches: 0.825
- Bullet Type: Full Metal Jacket
Ballistics:
- Ballistic Coefficient G1: 0.151
- Ballistic Coefficient G7: 0.076
- Use G7 Model: false
Penetration:
- Bullet Hardness: 15.0
- Penetration Energy Threshold: 58.0
```
#### B. Create Weapon Configuration Asset
1. **Right-click** in Content Browser → **Ballistics** → **Weapon Configuration**
2. **Name**: `BP_M4_WeaponConfig`
3. **Configure sections**:
**Weapon Info:**
```
- Weapon Name: "M4 Carbine"
- Manufacturer: "Colt"
- Model: "M4A1"
- Caliber: "5.56x45mm NATO"
- Description: "Standard issue carbine"
```
**Barrel Configuration:**
```
- Barrel Length: 14.5 inches
- Rifling Twist: 7.0 (1:7 twist)
- Bore Radius: 0.112 inches
- Muzzle Velocity Min: 91440 cm/s (~3000 fps)
- Muzzle Velocity Max: 91440 cm/s
- Inherent Accuracy: 0.001 MOA (radians)
- Spread Max: 0.001 radians
- Recoil Multiplier: 1.0
```
**Fire Control Configuration:**
```
- Available Fire Modes: [Semi-Auto, Full Auto]
- Default Fire Mode: Semi-Auto
- Fire Rate Min: 600 RPM
- Fire Rate Max: 750 RPM
- Burst Count: 3
- Has Safety: true
- Default Safety On: false
```
**Ammunition:**
```
- Default Bullet Type: BP_556NATO_BulletProperties
- Default Magazine Capacity: 30
- Allow Mixed Ammo: true
```
### 2. Create the Gun Blueprint
#### A. Create Blueprint Class
1. **Right-click** in Content Browser → **Blueprint Class**
2. **Search for**: `EBGun`
3. **Select**: `EBGun` class
4. **Name**: `BP_M4_Gun`
#### B. Configure Gun Components
1. **Open** `BP_M4_Gun` blueprint
2. **Components Panel**:
- **GunMesh**: Set your weapon's skeletal mesh
- **Barrel**: Automatically positioned at "MuzzleSocket"
- **Magazine**: Automatically created and configured
**Important**: Your weapon mesh must have a **"MuzzleSocket"** bone/socket for proper bullet spawning.
#### C. Set Weapon Configuration
1. **Select** the root `BP_M4_Gun(self)`
2. **Details Panel**:
- **Weapon Config**: Set to `BP_M4_WeaponConfig`
### 3. Blueprint Event Graph Setup
#### A. Basic Initialization
```blueprint
Event BeginPlay
├── Apply Weapon Configuration
│ └── Weapon Config: BP_M4_WeaponConfig
├── Load Magazine
│ └── Bullet Types: [BP_556NATO_BulletProperties x30]
├── Charging Handle
│ └── (Chambers first round)
└── Bind Events
├── Bind Event to OnGunFired
├── Bind Event to OnMagazineEmpty
├── Bind Event to OnGunStateChanged
└── Bind Event to OnSafetyChanged
```
#### B. Input Actions
```blueprint
InputAction Fire (Pressed)
├── Branch (Can Fire)
│ └── True: Pull Trigger
└── False: Print String "Cannot Fire"
InputAction Fire (Released)
└── Release Trigger
InputAction Reload (Pressed)
├── Branch (Rounds in Magazine < Max Capacity)
│ └── True: Reload Sequence
└── False: Print String "Magazine Full"
InputAction ToggleSafety (Pressed)
├── Get Safety State
├── NOT (Safety State)
└── Set Safety (Result)
InputAction ToggleFireMode (Pressed)
├── Get Fire Mode
├── Switch on Enum (Fire Mode)
│ ├── Semi-Auto: Set Fire Mode (Full Auto)
│ ├── Full Auto: Set Fire Mode (Semi-Auto)
│ └── Default: Set Fire Mode (Semi-Auto)
└── Print String ("Fire Mode: " + New Mode)
```
#### C. Reload System
```blueprint
Custom Event: Reload Sequence
├── Branch (Has Ammo in Inventory)
│ ├── True: Perform Reload
│ │ ├── Eject Magazine
│ │ ├── Delay (Reload Animation Time)
│ │ ├── Insert Magazine
│ │ │ └── New Magazine: Create New Magazine
│ │ ├── Charging Handle
│ │ └── Play Reload Sound
│ └── False: Play Empty Sound
└── Update Ammo UI
```
### 4. Event Response System
#### A. Gun State Management
```blueprint
OnGunStateChanged (NewState)
├── Switch on Enum (NewState)
│ ├── Ready:
│ │ ├── Set UI Color (Green)
│ │ ├── Hide Loading Indicator
│ │ └── Enable Fire Input
│ ├── Firing:
│ │ ├── Set UI Color (Red)
│ │ ├── Show Firing Indicator
│ │ └── Play Firing Animation
│ ├── Empty:
│ │ ├── Set UI Color (Gray)
│ │ ├── Show Reload Prompt
│ │ └── Disable Fire Input
│ ├── Jammed:
│ │ ├── Set UI Color (Yellow)
│ │ ├── Show Jam Indicator
│ │ └── Disable Fire Input
│ └── Safety On:
│ ├── Set UI Color (Orange)
│ ├── Show Safety Indicator
│ └── Disable Fire Input
└── Update Status Text
```
#### B. Firing Effects
```blueprint
OnGunFired (BulletType)
├── Spawn Emitter Attached
│ ├── Emitter Template: MuzzleFlash
│ ├── Attach to Component: GunMesh
│ └── Socket Name: MuzzleSocket
├── Play Sound 2D (GunFireSound)
├── Apply Camera Shake
│ ├── Shake Class: GunFireShake
│ └── Scale: 1.0
├── Add Impulse to Player
│ ├── Impulse: Calculate Recoil Impulse
│ └── Velocity Change: false
├── Spawn Actor (ShellCasing)
│ ├── Class: BP_ShellCasing
│ ├── Transform: EjectionPort Socket
│ └── Spawn Even if Colliding: true
└── Update Ammo Counter
```
#### C. Magazine Management
```blueprint
OnMagazineEmpty
├── Play Sound 2D (EmptyClickSound)
├── Show Reload Prompt UI
├── Set Can Fire (false)
└── Print String "Magazine Empty - Press R to Reload"
OnMagazineChanged (NewMagazine)
├── Branch (Is Valid: NewMagazine)
│ ├── True:
│ │ ├── Hide Reload Prompt
│ │ ├── Set Can Fire (true)
│ │ └── Play Reload Complete Sound
│ └── False:
│ ├── Show No Magazine Warning
│ └── Set Can Fire (false)
└── Update Magazine UI
```
### 5. Advanced Features
#### A. Multiple Ammunition Types
```blueprint
Function: Setup Mixed Ammo
├── Create Array (BulletTypes)
│ ├── Add Item: BP_556_FMJ x20
│ ├── Add Item: BP_556_AP x5
│ ├── Add Item: BP_556_HP x3
│ └── Add Item: BP_556_Tracer x2
├── Shuffle Array (BulletTypes)
└── Load Magazine (BulletTypes)
Function: Get Current Ammo Info
├── Get Chambered Bullet Type
├── Branch (Is Valid: Bullet Type)
│ ├── True: Get Bullet Info
│ │ ├── Bullet Name
│ │ ├── Damage Type
│ │ └── Special Properties
│ └── False: Return "No Round Chambered"
└── Update Ammo Type UI
```
#### B. Weapon Customization
```blueprint
Function: Apply Weapon Attachments
├── Input: Attachment Array
├── For Each Loop (Attachment)
│ ├── Switch on Attachment Type
│ │ ├── Scope:
│ │ │ ├── Modify Accuracy
│ │ │ └── Add Scope Mesh
│ │ ├── Suppressor:
│ │ │ ├── Reduce Noise
│ │ │ └── Modify Muzzle Flash
│ │ ├── Foregrip:
│ │ │ ├── Reduce Recoil
│ │ │ └── Improve Handling
│ │ └── Extended Magazine:
│ │ ├── Increase Capacity
│ │ └── Modify Reload Time
│ └── Update Weapon Stats
└── Refresh Weapon Model
```
#### C. Smart Targeting System
```blueprint
Function: Calculate Lead Target
├── Input: Target Actor
├── Get Target Velocity
├── Calculate Aim Direction
│ ├── Bullet Class: Current Bullet
│ ├── Target Location: Target Position
│ ├── Target Velocity: Movement Vector
│ └── Max Time: 10.0 seconds
├── Output: Predicted Aim Direction
└── Update Crosshair Position
Function: Predict Trajectory
├── Input: Aim Direction
├── Predict Hit
│ ├── Bullet Class: Current Bullet
│ ├── Max Time: 5.0 seconds
│ └── Step Size: 0.1 seconds
├── Output: Trajectory Points Array
└── Draw Trajectory Line
```
### 6. UI Integration
#### A. Weapon Status HUD
```blueprint
Widget: Weapon Status HUD
├── Ammo Counter
│ ├── Current Rounds: Get Rounds in Magazine
│ ├── Chambered Round: Get Chambered Bullet Type
│ └── Total Rounds: Get Total Rounds
├── Fire Mode Indicator
│ ├── Get Fire Mode
│ └── Display Mode Icon
├── Safety Indicator
│ ├── Get Safety State
│ └── Show/Hide Safety Icon
├── Weapon Status
│ ├── Get Gun State
│ └── Display Status Text
└── Crosshair
├── Dynamic Size: Based on Spread
└── Hit Indicator: Based on Trajectory
```
#### B. Weapon Selection Menu
```blueprint
Widget: Weapon Selection
├── For Each Available Weapon
│ ├── Create Weapon Button
│ ├── Set Weapon Icon
│ ├── Set Weapon Name
│ └── Bind Selection Event
├── On Weapon Selected
│ ├── Destroy Current Weapon
│ ├── Spawn New Weapon
│ └── Equip Weapon
└── Update Selection UI
```
### 7. Multiplayer Support
#### A. Network Setup
```blueprint
Event BeginPlay
├── Branch (Has Authority)
│ ├── True: Server Setup
│ │ ├── Initialize Weapon Systems
│ │ ├── Set Replication Properties
│ │ └── Enable Authority Functions
│ └── False: Client Setup
│ ├── Bind to Replicated Events
│ ├── Setup Client Prediction
│ └── Enable Input Handling
└── Configure Network Settings
├── Replicate Variables: true
├── Replicate Shot Events: true
└── Client Side Aim: true
```
#### B. Client Prediction
```blueprint
Function: Client Fire Prediction
├── Branch (Has Authority)
│ ├── True: Fire Immediately
│ └── False: Predicted Fire
│ ├── Play Effects Immediately
│ ├── Send Fire Request to Server
│ └── Wait for Server Confirmation
└── Handle Prediction Correction
```
### 8. Performance Optimization
#### A. LOD System
```blueprint
Event Tick
├── Branch (Should Update LOD)
│ └── True: Update LOD
│ ├── Get Distance to Player
│ ├── Calculate LOD Level
│ ├── Switch on LOD Level
│ │ ├── High: Full Detail
│ │ ├── Medium: Reduced Effects
│ │ └── Low: Minimal Detail
│ └── Apply LOD Settings
└── Set Should Update LOD (false)
```
#### B. Object Pooling
```blueprint
Manager: Weapon Pool
├── Initialize Pool
│ ├── Create Weapon Array
│ └── Pre-spawn Weapons
├── Get Weapon from Pool
│ ├── Find Available Weapon
│ ├── Configure Weapon
│ └── Return to Requester
└── Return Weapon to Pool
├── Reset Weapon State
└── Mark as Available
```
### 9. Debugging and Testing
#### A. Debug Visualization
```blueprint
Input Action: Toggle Debug
├── Branch (Debug Enabled)
│ ├── True: Disable Debug
│ │ ├── Clear Debug Display
│ │ └── Set Debug Enabled (false)
│ └── False: Enable Debug
│ ├── Show Trajectory
│ ├── Show Impact Points
│ ├── Show Weapon Stats
│ └── Set Debug Enabled (true)
└── Update Debug UI
```
#### B. Test Scenarios
```blueprint
Function: Run Weapon Tests
├── Test Basic Firing
│ ├── Load Magazine
│ ├── Fire Single Shot
│ └── Verify Bullet Spawned
├── Test Fire Modes
│ ├── Test Semi-Auto
│ ├── Test Full Auto
│ └── Test Burst Fire
├── Test Reload System
│ ├── Empty Magazine
│ ├── Reload Weapon
│ └── Verify New Ammo
└── Test Safety System
├── Enable Safety
├── Attempt Fire
└── Verify Block
```
### 10. Common Issues and Solutions
#### A. Troubleshooting Guide
**Gun Won't Fire:**
- Check if magazine is loaded: `Get Rounds in Magazine > 0`
- Verify round is chambered: `Get Chambered Bullet Type != None`
- Check safety state: `Get Safety State == false`
- Verify gun state: `Get Gun State == Ready`
**Bullets Don't Spawn:**
- Check MuzzleSocket exists on mesh
- Verify barrel component is attached
- Check bullet class is valid
- Verify barrel has parent gun reference
**Automatic Fire Won't Stop:**
- Ensure `Release Trigger` is called
- Check fire mode is set correctly
- Verify trigger input is properly bound
- Check for infinite loop in firing logic
**Networking Issues:**
- Verify replication settings are enabled
- Check server authority for critical functions
- Ensure client prediction is configured
- Test with multiple clients
### 11. Best Practices
#### A. Code Organization
1. **Separate Concerns**: Keep weapon logic, UI, and effects separate
2. **Use Events**: Leverage the gun's event system for responsiveness
3. **Validate Inputs**: Always check for valid references
4. **Handle Edge Cases**: Account for empty magazines, jams, etc.
#### B. Performance Considerations
1. **Limit Tick Usage**: Use events instead of tick when possible
2. **Pool Objects**: Reuse bullets, effects, and UI elements
3. **LOD Management**: Reduce detail for distant weapons
4. **Network Optimization**: Minimize replicated data
#### C. User Experience
1. **Responsive Feedback**: Provide immediate visual/audio feedback
2. **Clear State**: Always show current weapon state
3. **Intuitive Controls**: Use standard FPS control schemes
4. **Accessibility**: Support different input methods
## Conclusion
The Gun-Centric Architecture provides a comprehensive, realistic weapon system that handles all aspects of firearm behavior. By following this guide, you'll create fully functional weapons with proper ammunition management, multiple fire modes, and networking support.
The system is designed to be extensible, allowing you to add custom features while maintaining the core ballistic simulation accuracy that EasyBallistics is known for.
For additional examples and advanced techniques, refer to the other tutorials in this documentation set.
+10 -9
View File
@@ -38,15 +38,16 @@ const sidebars = {
'core-concepts/unit-conversions',
],
},
{
type: 'category',
label: 'Tutorials',
items: [
'tutorials/blueprint-integration',
'tutorials/material-setup-guide',
'tutorials/advanced-weapon-systems',
],
},
{
type: 'category',
label: 'Tutorials',
items: [
'tutorials/gun-blueprint-guide',
'tutorials/blueprint-integration',
'tutorials/material-setup-guide',
'tutorials/advanced-weapon-systems',
],
},
{
type: 'category',
label: 'Advanced Guides',