This commit is contained in:
2025-07-04 02:33:40 -07:00
parent a72231ee28
commit 1f0381506a
14 changed files with 2346 additions and 44 deletions
+17 -2
View File
@@ -23,10 +23,25 @@ FVector AEBBullet::UpdateVelocity_Implementation(UWorld* World, FVector Location
//drag
FVector relVel = (NewVelocity - GetWind(World, Location));
float speed = relVel.Size();
float mach = speed / speedOfSound;
// Safety check: Prevent division by zero and clamp mach number to reasonable range
float safeMach = (speedOfSound > 0.1f) ? (speed / speedOfSound) : 0.0f;
float mach = FMath::Clamp(safeMach, 0.0f, 50.0f); // Clamp to prevent extreme values
float profile = FMath::Pow(Diameter / 200.0f, 2.0f)*3.141592f;
float drag = GetCurveValue(MachDragCurve, mach, 0.25f)*FMath::Pow(speed / 100.0f, 2.0f)*profile*air*FormFactor*50.0f;
NewVelocity -= relVel.GetSafeNormal() * drag / Mass * DeltaTime / WorldScale;
// Safety check: Ensure WorldScale is not zero
float safeWorldScale = FMath::Max(WorldScale, 0.01f);
NewVelocity -= relVel.GetSafeNormal() * drag / Mass * DeltaTime / safeWorldScale;
// SAFETY: Prevent infinite or NaN velocities from physics calculations
if (!FMath::IsFinite(NewVelocity.X) || !FMath::IsFinite(NewVelocity.Y) || !FMath::IsFinite(NewVelocity.Z) ||
FMath::IsNaN(NewVelocity.X) || FMath::IsNaN(NewVelocity.Y) || FMath::IsNaN(NewVelocity.Z))
{
UE_LOG(LogTemp, Error, TEXT("EBBullet: Invalid velocity detected in UpdateVelocity, using previous velocity"));
return PreviousVelocity;
}
return NewVelocity;
}
@@ -31,6 +31,20 @@ FVector UEBBallisticImpactComponent::CalculateBallisticImpact(
bOutDidPenetrate = false;
OutExitLocation = ImpactLocation;
// SANITY CHECK: Validate input parameters
if (!FMath::IsFinite(ImpactLocation.X) || !FMath::IsFinite(ImpactLocation.Y) || !FMath::IsFinite(ImpactLocation.Z))
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Invalid impact location"));
return ImpactLocation;
}
if (!FMath::IsFinite(ProjectileVelocity.X) || !FMath::IsFinite(ProjectileVelocity.Y) || !FMath::IsFinite(ProjectileVelocity.Z) ||
ProjectileVelocity.IsNearlyZero())
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Invalid projectile velocity"));
return ImpactLocation;
}
if (!bEnableBallisticCalculations || !HitMaterial)
{
return ImpactLocation;
@@ -68,6 +82,22 @@ FVector UEBBallisticImpactComponent::CalculateBallisticImpact(
OutPenetrationDepth = BaseDepth * MaterialResponse.PenetrationDepthMultiplier;
}
// SANITY CHECK: Validate penetration depth
if (!FMath::IsFinite(OutPenetrationDepth) || OutPenetrationDepth < 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Invalid penetration depth %.3f, setting to 0"), OutPenetrationDepth);
OutPenetrationDepth = 0.0f;
}
// SANITY CHECK: Clamp to reasonable maximum (5 meters)
const float MaxPenetrationDepth = 500.0f; // 5 meters in cm
if (OutPenetrationDepth > MaxPenetrationDepth)
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Penetration depth %.1f cm exceeds maximum, clamping to %.1f cm"),
OutPenetrationDepth, MaxPenetrationDepth);
OutPenetrationDepth = MaxPenetrationDepth;
}
// Check if penetration occurred
float MinPenetrationThreshold = 1.0f; // 1cm minimum
bOutDidPenetrate = OutPenetrationDepth > MinPenetrationThreshold;
@@ -76,7 +106,19 @@ FVector UEBBallisticImpactComponent::CalculateBallisticImpact(
{
// Calculate exit location
FVector PenetrationDirection = ProjectileVelocity.GetSafeNormal();
OutExitLocation = ImpactLocation + (PenetrationDirection * OutPenetrationDepth);
FVector PenetrationVector = PenetrationDirection * OutPenetrationDepth;
// SANITY CHECK: Validate penetration vector
if (!FMath::IsFinite(PenetrationVector.X) || !FMath::IsFinite(PenetrationVector.Y) || !FMath::IsFinite(PenetrationVector.Z))
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Invalid penetration vector calculated"));
OutExitLocation = ImpactLocation;
bOutDidPenetrate = false;
}
else
{
OutExitLocation = ImpactLocation + PenetrationVector;
}
}
// Fire event
@@ -97,6 +139,27 @@ FVector UEBBallisticImpactComponent::CalculateRicochet(
OutEnergyRetained = 0.0f;
bOutDidRicochet = false;
// SANITY CHECK: Validate input parameters
if (!FMath::IsFinite(ImpactLocation.X) || !FMath::IsFinite(ImpactLocation.Y) || !FMath::IsFinite(ImpactLocation.Z))
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid impact location"));
return ProjectileVelocity;
}
if (!FMath::IsFinite(ImpactNormal.X) || !FMath::IsFinite(ImpactNormal.Y) || !FMath::IsFinite(ImpactNormal.Z) ||
ImpactNormal.IsNearlyZero())
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid impact normal"));
return ProjectileVelocity;
}
if (!FMath::IsFinite(ProjectileVelocity.X) || !FMath::IsFinite(ProjectileVelocity.Y) || !FMath::IsFinite(ProjectileVelocity.Z) ||
ProjectileVelocity.IsNearlyZero())
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid projectile velocity"));
return ProjectileVelocity;
}
if (!bEnableBallisticCalculations || !HitMaterial)
{
return ProjectileVelocity;
@@ -125,18 +188,73 @@ FVector UEBBallisticImpactComponent::CalculateRicochet(
// Calculate ricochet direction using reflection
FVector RicochetDirection = UKismetMathLibrary::GetReflectionVector(ProjectileVelocity, ImpactNormal);
// SANITY CHECK: Ensure reflection vector is valid
if (!FMath::IsFinite(RicochetDirection.X) || !FMath::IsFinite(RicochetDirection.Y) || !FMath::IsFinite(RicochetDirection.Z))
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid reflection vector, using simple reflection"));
RicochetDirection = ProjectileVelocity - 2.0f * FVector::DotProduct(ProjectileVelocity, ImpactNormal) * ImpactNormal;
}
// Apply some randomness based on material response
if (MaterialResponse.RicochetSpread > 0.0f)
{
FVector RandomOffset = FMath::VRand() * MaterialResponse.RicochetSpread;
// SAFETY: Clamp spread to reasonable range to prevent extreme deviations
float SafeSpread = FMath::Clamp(MaterialResponse.RicochetSpread, 0.0f, 45.0f);
FVector RandomOffset = FMath::VRand() * FMath::DegreesToRadians(SafeSpread);
RicochetDirection = (RicochetDirection + RandomOffset).GetSafeNormal();
}
// Calculate energy retention
OutEnergyRetained = MaterialResponse.RicochetRestitution;
// Calculate energy retention with additional safety clamping
OutEnergyRetained = FMath::Clamp(MaterialResponse.RicochetRestitution, 0.0f, 0.95f); // Never allow full energy retention
// Apply velocity reduction
FVector NewVelocity = RicochetDirection * (ProjectileVelocity.Size() * OutEnergyRetained);
// SANITY CHECK: Validate ricochet direction
if (!FMath::IsFinite(RicochetDirection.X) || !FMath::IsFinite(RicochetDirection.Y) || !FMath::IsFinite(RicochetDirection.Z) ||
RicochetDirection.IsNearlyZero())
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid ricochet direction calculated"));
return ProjectileVelocity;
}
// Apply velocity reduction with additional safety checks
float OriginalSpeed = ProjectileVelocity.Size();
// SAFETY: Clamp original speed to prevent calculation with invalid values
if (!FMath::IsFinite(OriginalSpeed) || OriginalSpeed <= 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid original speed %.3f"), OriginalSpeed);
return ProjectileVelocity;
}
float NewSpeed = OriginalSpeed * OutEnergyRetained;
// SANITY CHECK: Validate new speed
if (!FMath::IsFinite(NewSpeed) || NewSpeed < 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid ricochet speed %.3f"), NewSpeed);
return ProjectileVelocity;
}
// SAFETY: Additional clamp to prevent excessive speeds
float MaxSafeSpeed = OriginalSpeed * 0.9f; // Never exceed 90% of original speed
NewSpeed = FMath::Min(NewSpeed, MaxSafeSpeed);
FVector NewVelocity = RicochetDirection * NewSpeed;
// SANITY CHECK: Validate final ricochet velocity
if (!FMath::IsFinite(NewVelocity.X) || !FMath::IsFinite(NewVelocity.Y) || !FMath::IsFinite(NewVelocity.Z))
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid final ricochet velocity"));
return ProjectileVelocity;
}
// SAFETY: Final velocity magnitude check
float FinalSpeed = NewVelocity.Size();
if (!FMath::IsFinite(FinalSpeed) || FinalSpeed > OriginalSpeed)
{
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Ricochet velocity %.1f exceeds original %.1f, clamping"),
FinalSpeed, OriginalSpeed);
NewVelocity = NewVelocity.GetSafeNormal() * (OriginalSpeed * 0.8f);
}
// Fire event
OnBallisticRicochet.Broadcast(ImpactLocation, RicochetDirection, HitMaterial, OutEnergyRetained);
@@ -168,6 +286,17 @@ FEBMaterialResponseMapEntry UEBBallisticImpactComponent::GetMaterialResponse(UPh
if (!MaterialResponseMap || !PhysicalMaterial)
{
// CRITICAL FIX: Use conservative default values for unmapped materials to prevent infinite speed issues
DefaultResponse.RicochetRestitution = 0.3f; // Lower energy retention for safety
DefaultResponse.RicochetProbabilityMultiplier = 0.5f; // Reduced ricochet probability
DefaultResponse.RicochetSpread = 5.0f; // Add some randomness to prevent perfect reflections
DefaultResponse.NeverRicochet = false;
DefaultResponse.PenetrationDepthMultiplier = 0.8f; // Slightly reduced penetration
// Log warning about unmapped material
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Material '%s' not found in MaterialResponseMap, using safe defaults"),
PhysicalMaterial ? *PhysicalMaterial->GetName() : TEXT("NULL"));
return DefaultResponse;
}
@@ -176,6 +305,17 @@ FEBMaterialResponseMapEntry UEBBallisticImpactComponent::GetMaterialResponse(UPh
return *Found;
}
// CRITICAL FIX: Use conservative default values for unmapped materials to prevent infinite speed issues
DefaultResponse.RicochetRestitution = 0.3f; // Lower energy retention for safety
DefaultResponse.RicochetProbabilityMultiplier = 0.5f; // Reduced ricochet probability
DefaultResponse.RicochetSpread = 5.0f; // Add some randomness to prevent perfect reflections
DefaultResponse.NeverRicochet = false;
DefaultResponse.PenetrationDepthMultiplier = 0.8f; // Slightly reduced penetration
// Log warning about unmapped material
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Material '%s' not found in MaterialResponseMap, using safe defaults"),
*PhysicalMaterial->GetName());
return DefaultResponse;
}
+188 -1
View File
@@ -1,5 +1,7 @@
// Copyright 2018 Mookie. All Rights Reserved.
#include "EBBullet.h"
#include "EBBarrel.h"
#include "EBUnitConversions.h"
// Sets default values
AEBBullet::AEBBullet() {
@@ -7,6 +9,9 @@ AEBBullet::AEBBullet() {
PrimaryActorTick.bCanEverTick = true;
SetTickGroup(ETickingGroup::TG_PrePhysics);
// Set bullet lifetime to 10 seconds
InitialLifeSpan = 10.0f;
// Initialize firing barrel pointer
FiringBarrel = nullptr;
@@ -75,6 +80,56 @@ void AEBBullet::Tick(float DeltaTime) {
void AEBBullet::Step(float DeltaTime) {
FVector start = GetActorLocation();
bool sendUpdate = false;
// SANITY CHECK: Validate delta time to prevent simulation instability
if (DeltaTime <= 0.0f || DeltaTime > 1.0f || !FMath::IsFinite(DeltaTime))
{
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Invalid DeltaTime %.6f, skipping step"), DeltaTime);
return;
}
// SANITY CHECK: Distance-based cleanup - bullets that travel too far should be destroyed
FVector SpawnLocation = GetFiringBarrel() ? GetFiringBarrel()->GetComponentLocation() : FVector::ZeroVector;
float MaxTravelDistance = IsSpallFragment ? 50000.0f : 1000000.0f; // 500m for fragments, 10km for regular bullets
float TravelDistance = FVector::Dist(start, SpawnLocation);
if (TravelDistance > MaxTravelDistance)
{
UE_LOG(LogTemp, Log, TEXT("EBBullet: Destroying bullet that traveled too far (%.1f cm > %.1f cm)"), TravelDistance, MaxTravelDistance);
Deactivate();
return;
}
// SANITY CHECK: Validate current location
if (!start.IsZero() && (!FMath::IsFinite(start.X) || !FMath::IsFinite(start.Y) || !FMath::IsFinite(start.Z)))
{
UE_LOG(LogTemp, Error, TEXT("EBBullet: Invalid position detected, destroying bullet"));
Deactivate();
return;
}
// SANITY CHECK: Validate velocity before processing
if (!FMath::IsFinite(Velocity.X) || !FMath::IsFinite(Velocity.Y) || !FMath::IsFinite(Velocity.Z) ||
FMath::IsNaN(Velocity.X) || FMath::IsNaN(Velocity.Y) || FMath::IsNaN(Velocity.Z))
{
UE_LOG(LogTemp, Error, TEXT("EBBullet: Invalid velocity detected, destroying bullet"));
Deactivate();
return;
}
// SANITY CHECK: Validate mass and physics properties
float EffectiveMass = GetEffectiveMass();
if (EffectiveMass <= 0.0f || !FMath::IsFinite(EffectiveMass))
{
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Invalid mass %.6f, using default 0.004kg"), EffectiveMass);
Mass = 0.004f; // Default to 4 grams
}
// Initialize debug tracking
if (DebugEnabled)
{
DebugTraceCounter++;
DebugFrameStartTime = GetWorld()->GetTimeSeconds();
}
if (Retrace && CanRetrace) {
//time travel
@@ -105,7 +160,51 @@ void AEBBullet::Step(float DeltaTime) {
CanRetrace = false;
FVector PreviousVelocity = Velocity;
Velocity = UpdateVelocity(GetWorld(), GetActorLocation(), Velocity, DeltaTime);
FVector CurrentLocation = GetActorLocation();
Velocity = UpdateVelocity(GetWorld(), CurrentLocation, Velocity, DeltaTime);
// Debug trajectory visualization
if (DebugEnabled && DebugTrajectory)
{
FVector EndLocation = CurrentLocation + Velocity * DeltaTime;
DrawTrajectoryDebug(start, EndLocation, Velocity, DeltaTime);
}
// Debug physics forces
if (DebugEnabled && DebugPhysics)
{
// Calculate forces for debug display
FVector GravityForce = OverrideGravity ? Gravity : FVector(0, 0, GetWorld()->GetGravityZ());
GravityForce *= GetEffectiveMass() * DeltaTime;
FVector WindVelocity = GetWind(GetWorld(), CurrentLocation);
FVector RelativeVelocity = Velocity - WindVelocity;
float AirDensity = GetAirDensity(GetWorld(), CurrentLocation);
float SpeedOfSound = GetSpeedOfSound(GetWorld(), CurrentLocation);
float MachNumber = RelativeVelocity.Size() / SpeedOfSound;
float DragCoeff = GetEffectiveDragCoefficient(MachNumber);
// Calculate drag force: F = 0.5 * Cd * ρ * A * v²
float CrossSectionArea = PI * FMath::Pow(GetEffectiveDiameter() / 200.0f, 2.0f); // Convert cm to m and get area
FVector DragForce = -RelativeVelocity.GetSafeNormal() * 0.5f * DragCoeff * AirDensity * CrossSectionArea * FMath::Pow(RelativeVelocity.Size() / 100.0f, 2.0f);
DragForce *= DeltaTime;
FVector WindForce = (WindVelocity - Velocity) * 0.1f * DeltaTime; // Simplified wind effect
DrawPhysicsDebug(CurrentLocation, DragForce, GravityForce, WindForce, AirDensity, SpeedOfSound);
}
// Debug ballistics calculations
if (DebugEnabled && DebugBallistics)
{
float VelocityMPS = FEBUnitConversions::CMPSToMPS(Velocity.Size());
float KineticEnergy = FEBUnitConversions::CalculateKineticEnergyJoules(GetEffectiveMass(), VelocityMPS);
float SpeedOfSound = GetSpeedOfSound(GetWorld(), CurrentLocation);
float MachNumber = Velocity.Size() / SpeedOfSound;
float DragCoeff = GetEffectiveDragCoefficient(MachNumber);
DrawBallisticsDebug(CurrentLocation, Velocity, KineticEnergy, DragCoeff, MachNumber);
}
//trace
float remainingTime = DeltaTime;
@@ -142,6 +241,14 @@ void AEBBullet::Step(float DeltaTime) {
NewRot.Roll = GetActorRotation().Roll;
SetActorRotation(NewRot);
}
// Debug performance metrics
if (DebugEnabled && DebugPerformance)
{
float FrameTime = GetWorld()->GetTimeSeconds() - DebugFrameStartTime;
int32 PooledBullets = EnablePooling ? Pooled.Num() : 0;
DrawPerformanceDebug(GetActorLocation(), DebugTraceCounter, FrameTime, PooledBullets);
}
}
float AEBBullet::GetCurveValue(const UCurveFloat* curve, float in, float deflt) const {
@@ -283,6 +390,34 @@ void AEBBullet::GenerateSpallFragments(FVector ImpactLocation, FVector ImpactVel
return;
}
// SANITY CHECK: Validate input parameters
if (!FMath::IsFinite(ImpactLocation.X) || !FMath::IsFinite(ImpactLocation.Y) || !FMath::IsFinite(ImpactLocation.Z) ||
!FMath::IsFinite(ImpactVelocity.X) || !FMath::IsFinite(ImpactVelocity.Y) || !FMath::IsFinite(ImpactVelocity.Z) ||
!FMath::IsFinite(ImpactNormal.X) || !FMath::IsFinite(ImpactNormal.Y) || !FMath::IsFinite(ImpactNormal.Z))
{
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Invalid spalling parameters, skipping fragment generation"));
return;
}
// SANITY CHECK: Prevent excessive fragment generation for performance
static int32 GlobalFragmentCount = 0;
static float LastFrameTime = 0.0f;
float CurrentFrameTime = GetWorld()->GetTimeSeconds();
// Reset counter every second
if (CurrentFrameTime - LastFrameTime > 1.0f)
{
GlobalFragmentCount = 0;
LastFrameTime = CurrentFrameTime;
}
const int32 MaxFragmentsPerSecond = 200; // Performance limit
if (GlobalFragmentCount > MaxFragmentsPerSecond)
{
UE_LOG(LogTemp, Log, TEXT("EBBullet: Fragment generation limit reached this second (%d), skipping"), MaxFragmentsPerSecond);
return;
}
const FEBMaterialResponseMapEntry* ResponseEntry = MaterialResponseMap->Map.Find(Material);
if (!ResponseEntry)
{
@@ -342,10 +477,27 @@ void AEBBullet::GenerateSpallFragments(FVector ImpactLocation, FVector ImpactVel
SpreadAngleRad = FMath::DegreesToRadians(PhysicalSpreadAngle);
}
// SANITY CHECK: Clamp fragment count to reasonable limits
const int32 MaxFragmentsPerImpact = 20; // Absolute maximum per single impact
FragmentCount = FMath::Clamp(FragmentCount, 0, MaxFragmentsPerImpact);
if (FragmentCount <= 0)
{
return;
}
// Base spalling direction (combination of reflection and surface normal)
FVector ReflectionDirection = UKismetMathLibrary::GetReflectionVector(ImpactVelocity.GetSafeNormal(), ImpactNormal);
FVector SpallBaseDirection = FMath::Lerp(ImpactNormal, ReflectionDirection, 0.3f).GetSafeNormal();
// SANITY CHECK: Validate spall direction
if (!FMath::IsFinite(SpallBaseDirection.X) || !FMath::IsFinite(SpallBaseDirection.Y) || !FMath::IsFinite(SpallBaseDirection.Z) ||
SpallBaseDirection.IsNearlyZero())
{
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Invalid spall direction calculated, using impact normal"));
SpallBaseDirection = ImpactNormal.GetSafeNormal();
}
for (int32 i = 0; i < FragmentCount; i++)
{
float FragmentMass;
@@ -389,9 +541,37 @@ void AEBBullet::GenerateSpallFragments(FVector ImpactLocation, FVector ImpactVel
FragmentSpeed = FMath::Sqrt(2.0f * FragmentKineticEnergy / FragmentMass);
}
// SANITY CHECK: Validate fragment properties
if (FragmentMass <= 0.0f || !FMath::IsFinite(FragmentMass))
{
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Invalid fragment mass %.6f, skipping fragment %d"), FragmentMass, i);
continue;
}
if (FragmentSpeed <= 0.0f || !FMath::IsFinite(FragmentSpeed) || FragmentSpeed > 500000.0f) // Max 5km/s
{
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Invalid fragment speed %.1f, clamping to reasonable range"), FragmentSpeed);
FragmentSpeed = FMath::Clamp(FragmentSpeed, 100.0f, 100000.0f); // 1-1000 m/s range
}
// SANITY CHECK: Check global fragment limit before spawning
if (GlobalFragmentCount >= MaxFragmentsPerSecond)
{
UE_LOG(LogTemp, Log, TEXT("EBBullet: Global fragment limit reached, stopping fragment generation"));
break;
}
// Fragment direction with dispersion
FVector FragmentDirection = RandomStream.VRandCone(SpallBaseDirection, SpreadAngleRad);
FVector FragmentVelocity = FragmentDirection * FragmentSpeed;
// SANITY CHECK: Validate fragment velocity
if (!FMath::IsFinite(FragmentVelocity.X) || !FMath::IsFinite(FragmentVelocity.Y) || !FMath::IsFinite(FragmentVelocity.Z) ||
FragmentVelocity.IsNearlyZero())
{
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Invalid fragment velocity, skipping fragment %d"), i);
continue;
}
FVector SpawnLocation = ImpactLocation + ImpactNormal * 2.0f;
@@ -453,6 +633,9 @@ void AEBBullet::GenerateSpallFragments(FVector ImpactLocation, FVector ImpactVel
Fragment->Velocity = FragmentVelocity;
}
// SANITY CHECK: Increment global fragment counter
GlobalFragmentCount++;
// Debug logging for spall fragment creation
if (DebugEnabled)
{
@@ -462,6 +645,10 @@ void AEBBullet::GenerateSpallFragments(FVector ImpactLocation, FVector ImpactVel
Fragment->EnableSpalling ? TEXT("true") : TEXT("false"));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Failed to spawn spall fragment"));
}
}
OnSpallFragmentGenerated(SpawnLocation, FragmentVelocity, FragmentMass, Material);
@@ -83,6 +83,12 @@ void AEBBullet::CreateDebugImpactWidget(FVector Location, bool bRicochet, bool b
PenetrationDepth
);
// Fix color conversion for display
uint8 R = FMath::Clamp(FMath::RoundToInt(ImpactColor.R * 255.0f), 0, 255);
uint8 G = FMath::Clamp(FMath::RoundToInt(ImpactColor.G * 255.0f), 0, 255);
uint8 B = FMath::Clamp(FMath::RoundToInt(ImpactColor.B * 255.0f), 0, 255);
FColor DisplayColor(R, G, B);
// Display debug information
if (UseOnScreenMessages && GEngine)
{
@@ -93,14 +99,13 @@ void AEBBullet::CreateDebugImpactWidget(FVector Location, bool bRicochet, bool b
}
GEngine->AddOnScreenDebugMessage(DebugMessageCounter++, ImpactDebugTime,
FColor(ImpactColor.R * 255, ImpactColor.G * 255, ImpactColor.B * 255), DebugInfo);
DisplayColor, DebugInfo);
}
if (UseWorldSpaceText)
{
DrawDebugString(World, Location + FVector(0, 0, 50), DebugInfo, nullptr,
FColor(ImpactColor.R * 255, ImpactColor.G * 255, ImpactColor.B * 255),
ImpactDebugTime, false, WorldTextScale);
DisplayColor, ImpactDebugTime, false, WorldTextScale);
}
}
@@ -146,10 +151,15 @@ void AEBBullet::DrawTrajectoryDebug(FVector StartLocation, FVector EndLocation,
FLinearColor TrailColor = FMath::Lerp(DebugTrailColorSlow, DebugTrailColorFast, VelocityNormalized);
TrailColor.A = 1.0f - Alpha;
// Fix color conversion - ensure valid range [0-255]
uint8 R = FMath::Clamp(FMath::RoundToInt(TrailColor.R * 255.0f), 0, 255);
uint8 G = FMath::Clamp(FMath::RoundToInt(TrailColor.G * 255.0f), 0, 255);
uint8 B = FMath::Clamp(FMath::RoundToInt(TrailColor.B * 255.0f), 0, 255);
uint8 A = FMath::Clamp(FMath::RoundToInt(TrailColor.A * 255.0f), 0, 255);
float LineThickness = DebugTrailWidth > 0 ? DebugTrailWidth : 1.0f;
DrawDebugLine(World, DebugTrajectoryPoints[i-1], DebugTrajectoryPoints[i],
FColor(TrailColor.R * 255, TrailColor.G * 255, TrailColor.B * 255, TrailColor.A * 255),
false, -1, 0, LineThickness);
FColor(R, G, B, A), false, -1, 0, LineThickness);
}
}
}
@@ -192,6 +202,72 @@ void AEBBullet::DrawTrajectoryDebug(FVector StartLocation, FVector EndLocation,
}
}
void AEBBullet::DrawXRayTrajectory(FVector EntryPoint, FVector ExitPoint, FVector PenetrationDirection, float PenetrationDepth, UPhysicalMaterial* Material)
{
if (!DebugEnabled || !DebugTrajectory || !ShowXRayTrajectory)
{
return;
}
UWorld* World = GetWorld();
if (!World)
{
return;
}
// Draw entry point marker
uint8 XR = FMath::Clamp(FMath::RoundToInt(XRayTrajectoryColor.R * 255.0f), 0, 255);
uint8 XG = FMath::Clamp(FMath::RoundToInt(XRayTrajectoryColor.G * 255.0f), 0, 255);
uint8 XB = FMath::Clamp(FMath::RoundToInt(XRayTrajectoryColor.B * 255.0f), 0, 255);
uint8 XA = FMath::Clamp(FMath::RoundToInt(XRayTrajectoryColor.A * 255.0f), 0, 255);
FColor XRayColor(XR, XG, XB, XA);
DrawDebugSphere(World, EntryPoint, 3.0f, 8, XRayColor, false, DebugTrailTime);
// Draw internal trajectory path (X-ray view)
FVector InternalTrajectory = EntryPoint + PenetrationDirection * PenetrationDepth;
// Draw dashed line to represent path through material
int32 DashCount = FMath::Max(1, FMath::RoundToInt(PenetrationDepth / 5.0f)); // Dash every 5cm
for (int32 i = 0; i < DashCount; i++)
{
float StartRatio = (float)i / DashCount;
float EndRatio = FMath::Min(1.0f, (float)(i + 0.7f) / DashCount); // 70% dash, 30% gap
FVector DashStart = FMath::Lerp(EntryPoint, InternalTrajectory, StartRatio);
FVector DashEnd = FMath::Lerp(EntryPoint, InternalTrajectory, EndRatio);
DrawDebugLine(World, DashStart, DashEnd, XRayColor, false, DebugTrailTime, 0, 2.0f);
}
// Draw exit point if it exists
if (!ExitPoint.IsNearlyZero() && !EntryPoint.Equals(ExitPoint, 1.0f))
{
uint8 PR = FMath::Clamp(FMath::RoundToInt(PenetrationTrajectoryColor.R * 255.0f), 0, 255);
uint8 PG = FMath::Clamp(FMath::RoundToInt(PenetrationTrajectoryColor.G * 255.0f), 0, 255);
uint8 PB = FMath::Clamp(FMath::RoundToInt(PenetrationTrajectoryColor.B * 255.0f), 0, 255);
uint8 PA = FMath::Clamp(FMath::RoundToInt(PenetrationTrajectoryColor.A * 255.0f), 0, 255);
FColor PenetrationColor(PR, PG, PB, PA);
DrawDebugSphere(World, ExitPoint, 3.0f, 8, PenetrationColor, false, DebugTrailTime);
// Draw penetration path from calculated internal point to actual exit
DrawDebugLine(World, InternalTrajectory, ExitPoint, PenetrationColor, false, DebugTrailTime, 0, 2.0f);
}
// Add material and penetration info
if (Material && UseWorldSpaceText)
{
FString MaterialInfo = FString::Printf(TEXT("Material: %s\nPenetration: %.1f cm\nPath Length: %.1f cm"),
*Material->GetName(),
PenetrationDepth,
FVector::Dist(EntryPoint, ExitPoint));
DrawDebugString(World, EntryPoint + FVector(0, 0, 25), MaterialInfo, nullptr,
XRayColor, DebugTrailTime, false, WorldTextScale);
}
}
void AEBBullet::DrawImpactDebug(FVector ImpactLocation, FVector ImpactVelocity, FVector ImpactNormal, bool bRicochet, bool bPenetration, float PenetrationDepth, UPhysicalMaterial* Material)
{
if (!DebugEnabled || !DebugImpact)
@@ -220,7 +296,11 @@ void AEBBullet::DrawImpactDebug(FVector ImpactLocation, FVector ImpactVelocity,
ImpactColor = ImpactColorStopped;
}
FColor DebugColor(ImpactColor.R * 255, ImpactColor.G * 255, ImpactColor.B * 255);
// Fix color conversion
uint8 R = FMath::Clamp(FMath::RoundToInt(ImpactColor.R * 255.0f), 0, 255);
uint8 G = FMath::Clamp(FMath::RoundToInt(ImpactColor.G * 255.0f), 0, 255);
uint8 B = FMath::Clamp(FMath::RoundToInt(ImpactColor.B * 255.0f), 0, 255);
FColor DebugColor(R, G, B);
// Show impact points
if (ShowImpactPoints)
@@ -400,8 +480,16 @@ void AEBBullet::DrawSpallDebug(FVector ImpactLocation, FVector ImpactVelocity, F
return;
}
FColor PrimaryColor(SpallColorPrimary.R * 255, SpallColorPrimary.G * 255, SpallColorPrimary.B * 255);
FColor FragmentColor(SpallColorFragment.R * 255, SpallColorFragment.G * 255, SpallColorFragment.B * 255);
// Fix color conversion for spall colors
uint8 PR = FMath::Clamp(FMath::RoundToInt(SpallColorPrimary.R * 255.0f), 0, 255);
uint8 PG = FMath::Clamp(FMath::RoundToInt(SpallColorPrimary.G * 255.0f), 0, 255);
uint8 PB = FMath::Clamp(FMath::RoundToInt(SpallColorPrimary.B * 255.0f), 0, 255);
FColor PrimaryColor(PR, PG, PB);
uint8 FR = FMath::Clamp(FMath::RoundToInt(SpallColorFragment.R * 255.0f), 0, 255);
uint8 FG = FMath::Clamp(FMath::RoundToInt(SpallColorFragment.G * 255.0f), 0, 255);
uint8 FB = FMath::Clamp(FMath::RoundToInt(SpallColorFragment.B * 255.0f), 0, 255);
FColor FragmentColor(FR, FG, FB);
// Show spall generation
if (ShowSpallGeneration)
@@ -11,12 +11,52 @@ float UEBMathematicalBallistics::CalculatePenetrationDepth(
float VelocityMPS,
float ImpactAngleDegrees)
{
// SANITY CHECK: Validate input parameters
if (VelocityMPS <= 0.0f || !FMath::IsFinite(VelocityMPS) || VelocityMPS > 10000.0f) // Max 10 km/s
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid velocity %.3f m/s for penetration calculation"), VelocityMPS);
return 0.0f;
}
if (!FMath::IsFinite(ImpactAngleDegrees) || ImpactAngleDegrees < 0.0f || ImpactAngleDegrees > 90.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid impact angle %.1f degrees, clamping to valid range"), ImpactAngleDegrees);
ImpactAngleDegrees = FMath::Clamp(ImpactAngleDegrees, 0.0f, 90.0f);
}
// SANITY CHECK: Validate material properties
if (MaterialProps.DensityGPerCm3 <= 0.0f || !FMath::IsFinite(MaterialProps.DensityGPerCm3))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid material density %.3f g/cm³"), MaterialProps.DensityGPerCm3);
return 0.0f;
}
if (MaterialProps.YieldStrengthMPa <= 0.0f || !FMath::IsFinite(MaterialProps.YieldStrengthMPa))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid yield strength %.1f MPa"), MaterialProps.YieldStrengthMPa);
return 0.0f;
}
// Calculate kinetic energy in joules
float KineticEnergy = CalculateKineticEnergy(BulletProps, VelocityMPS);
// SANITY CHECK: Validate kinetic energy
if (KineticEnergy <= 0.0f || !FMath::IsFinite(KineticEnergy))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid kinetic energy %.3f J"), KineticEnergy);
return 0.0f;
}
// Calculate sectional density in proper SI units (kg/m²)
float SectionalDensity = BulletProps.GetSectionalDensityKgPerM2();
// SANITY CHECK: Validate sectional density
if (SectionalDensity <= 0.0f || !FMath::IsFinite(SectionalDensity))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid sectional density %.6f kg/m²"), SectionalDensity);
return 0.0f;
}
// Calculate hardness ratio
float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness);
@@ -40,15 +80,52 @@ float UEBMathematicalBallistics::CalculatePenetrationDepth(
float DensityKgPerM3 = FEBUnitConversions::GPerCM3ToKGPerM3(MaterialProps.DensityGPerCm3);
float YieldStrengthPa = FEBUnitConversions::MPaToPa(MaterialProps.YieldStrengthMPa);
// SANITY CHECK: Validate converted values
if (DensityKgPerM3 <= 0.0f || !FMath::IsFinite(DensityKgPerM3) ||
YieldStrengthPa <= 0.0f || !FMath::IsFinite(YieldStrengthPa))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid converted material properties"));
return 0.0f;
}
// Empirical constant adjusted for proper units (results in meters)
float Constant = 2.0f; // Adjusted for dimensional consistency
float PenetrationM = (Constant * KineticEnergy * SectionalDensity * HardnessRatio * AngleFactor * ShapeFactor) /
(DensityKgPerM3 * YieldStrengthPa);
// SANITY CHECK: Prevent division by zero and validate all factors
float Denominator = DensityKgPerM3 * YieldStrengthPa;
if (Denominator <= 0.0f || !FMath::IsFinite(Denominator))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid denominator in penetration calculation"));
return 0.0f;
}
float Numerator = Constant * KineticEnergy * SectionalDensity * HardnessRatio * AngleFactor * ShapeFactor;
if (!FMath::IsFinite(Numerator))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid numerator in penetration calculation"));
return 0.0f;
}
float PenetrationM = Numerator / Denominator;
// SANITY CHECK: Validate result before conversion
if (!FMath::IsFinite(PenetrationM) || PenetrationM < 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid penetration result %.6f m"), PenetrationM);
return 0.0f;
}
// Convert from meters to centimeters for consistency with Unreal Engine units
float PenetrationCm = FEBUnitConversions::MetersToCM(PenetrationM);
// SANITY CHECK: Clamp to reasonable maximum (10 meters = 1000 cm)
const float MaxPenetrationCm = 1000.0f;
if (PenetrationCm > MaxPenetrationCm)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Penetration %.1f cm exceeds maximum, clamping to %.1f cm"), PenetrationCm, MaxPenetrationCm);
PenetrationCm = MaxPenetrationCm;
}
return FMath::Max(0.0f, PenetrationCm);
}
@@ -59,25 +136,87 @@ float UEBMathematicalBallistics::CalculateResidualVelocity(
float ThicknessCM,
float ImpactAngleDegrees)
{
// SANITY CHECK: Validate input parameters
if (VelocityMPS <= 0.0f || !FMath::IsFinite(VelocityMPS))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid velocity %.3f m/s for residual velocity calculation"), VelocityMPS);
return 0.0f;
}
if (ThicknessCM <= 0.0f || !FMath::IsFinite(ThicknessCM))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid thickness %.3f cm"), ThicknessCM);
return 0.0f;
}
// Calculate ballistic limit velocity
float BallisticLimit = CalculateRechtIpsonVelocity(BulletProps, MaterialProps, ThicknessCM);
// SANITY CHECK: Validate ballistic limit
if (!FMath::IsFinite(BallisticLimit) || BallisticLimit < 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid ballistic limit %.3f m/s"), BallisticLimit);
return 0.0f;
}
// Adjust for impact angle
float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees);
// SANITY CHECK: Prevent division by zero
if (AngleFactor <= 0.001f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Angle factor too small %.6f, using minimum"), AngleFactor);
AngleFactor = 0.001f;
}
BallisticLimit = BallisticLimit / AngleFactor;
// SANITY CHECK: Validate adjusted ballistic limit
if (!FMath::IsFinite(BallisticLimit))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid adjusted ballistic limit"));
return 0.0f;
}
// If impact velocity is below ballistic limit, no penetration
if (VelocityMPS <= BallisticLimit)
{
return 0.0f;
}
// SANITY CHECK: Ensure square root argument is positive
float VelocitySquared = VelocityMPS * VelocityMPS;
float BallisticLimitSquared = BallisticLimit * BallisticLimit;
float SquareRootArgument = VelocitySquared - BallisticLimitSquared;
if (SquareRootArgument < 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Negative square root argument %.6f, velocity %.3f < ballistic limit %.3f"),
SquareRootArgument, VelocityMPS, BallisticLimit);
return 0.0f;
}
// Calculate residual velocity using Recht-Ipson equation
// Vr = sqrt(V^2 - Vbl^2)
float ResidualVelocity = FMath::Sqrt(VelocityMPS * VelocityMPS - BallisticLimit * BallisticLimit);
float ResidualVelocity = FMath::Sqrt(SquareRootArgument);
// SANITY CHECK: Validate residual velocity before applying absorption
if (!FMath::IsFinite(ResidualVelocity))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid residual velocity calculated"));
return 0.0f;
}
// SANITY CHECK: Validate energy absorption coefficient
float EnergyAbsorption = FMath::Clamp(MaterialProps.EnergyAbsorptionCoefficient, 0.0f, 0.99f);
if (EnergyAbsorption != MaterialProps.EnergyAbsorptionCoefficient)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Energy absorption coefficient %.3f clamped to %.3f"),
MaterialProps.EnergyAbsorptionCoefficient, EnergyAbsorption);
}
// Apply energy absorption factor
ResidualVelocity *= (1.0f - MaterialProps.EnergyAbsorptionCoefficient);
ResidualVelocity *= (1.0f - EnergyAbsorption);
return FMath::Max(0.0f, ResidualVelocity);
}
@@ -209,6 +348,19 @@ float UEBMathematicalBallistics::CalculateRechtIpsonVelocity(
const FMathematicalMaterialProperties& MaterialProps,
float ThicknessCM)
{
// SANITY CHECK: Validate input parameters
if (ThicknessCM <= 0.0f || !FMath::IsFinite(ThicknessCM))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid thickness %.3f cm for Recht-Ipson calculation"), ThicknessCM);
return 0.0f;
}
if (MaterialProps.YieldStrengthMPa <= 0.0f || !FMath::IsFinite(MaterialProps.YieldStrengthMPa))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid yield strength %.1f MPa"), MaterialProps.YieldStrengthMPa);
return 0.0f;
}
// Recht-Ipson equation for ballistic limit velocity
// Vbl = sqrt(k * σ * t / (ρ * A))
// Where k is a constant, σ is yield strength, t is thickness, ρ is bullet density, A is cross-sectional area
@@ -220,13 +372,75 @@ float UEBMathematicalBallistics::CalculateRechtIpsonVelocity(
float ThicknessM = ThicknessCM * 0.01f; // cm to m
float YieldStrengthPa = MaterialProps.YieldStrengthMPa * 1000000.0f; // MPa to Pa
// SANITY CHECK: Validate cross-sectional area
if (CrossSectionAreaM2 <= 0.0f || !FMath::IsFinite(CrossSectionAreaM2))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid cross-sectional area %.8f m²"), CrossSectionAreaM2);
return 0.0f;
}
// Calculate bullet density (kg/m³)
float BulletVolumeM3 = CrossSectionAreaM2 * BulletProps.LengthInches * 0.0254f; // Convert length to meters
float BulletDensityKgPerM3 = BulletProps.GetMassKg() / BulletVolumeM3;
// SANITY CHECK: Validate bullet volume
if (BulletVolumeM3 <= 0.0f || !FMath::IsFinite(BulletVolumeM3))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid bullet volume %.8f m³"), BulletVolumeM3);
return 0.0f;
}
float BulletMassKg = BulletProps.GetMassKg();
if (BulletMassKg <= 0.0f || !FMath::IsFinite(BulletMassKg))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid bullet mass %.6f kg"), BulletMassKg);
return 0.0f;
}
float BulletDensityKgPerM3 = BulletMassKg / BulletVolumeM3;
// SANITY CHECK: Validate bullet density
if (BulletDensityKgPerM3 <= 0.0f || !FMath::IsFinite(BulletDensityKgPerM3))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid bullet density %.3f kg/m³"), BulletDensityKgPerM3);
return 0.0f;
}
// SANITY CHECK: Calculate and validate square root argument
float Numerator = Constant * YieldStrengthPa * ThicknessM;
float Denominator = BulletDensityKgPerM3 * CrossSectionAreaM2;
if (Denominator <= 0.0f || !FMath::IsFinite(Denominator))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid denominator %.6f in Recht-Ipson equation"), Denominator);
return 0.0f;
}
float SquareRootArgument = Numerator / Denominator;
if (SquareRootArgument < 0.0f || !FMath::IsFinite(SquareRootArgument))
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid square root argument %.6f in Recht-Ipson equation"), SquareRootArgument);
return 0.0f;
}
// Calculate ballistic limit velocity in m/s
float BallisticLimitMPS = FMath::Sqrt(Constant * YieldStrengthPa * ThicknessM /
(BulletDensityKgPerM3 * CrossSectionAreaM2));
float BallisticLimitMPS = FMath::Sqrt(SquareRootArgument);
// SANITY CHECK: Validate final result
if (!FMath::IsFinite(BallisticLimitMPS) || BallisticLimitMPS < 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid ballistic limit velocity %.3f m/s"), BallisticLimitMPS);
return 0.0f;
}
// SANITY CHECK: Clamp to reasonable maximum (5 km/s)
const float MaxBallisticLimit = 5000.0f;
if (BallisticLimitMPS > MaxBallisticLimit)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Ballistic limit %.1f m/s exceeds maximum, clamping to %.1f m/s"),
BallisticLimitMPS, MaxBallisticLimit);
BallisticLimitMPS = MaxBallisticLimit;
}
return BallisticLimitMPS;
}
@@ -254,14 +468,44 @@ float UEBMathematicalBallistics::CalculateCriticalRicochetAngle(
// Helper function implementations
float UEBMathematicalBallistics::CalculateHardnessRatio(float BulletHardness, float MaterialHardness)
{
// SANITY CHECK: Validate hardness values
if (!FMath::IsFinite(BulletHardness) || BulletHardness < 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid bullet hardness %.3f, using default 15.0"), BulletHardness);
BulletHardness = 15.0f; // Default bullet hardness
}
if (!FMath::IsFinite(MaterialHardness) || MaterialHardness <= 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid material hardness %.3f, using default 50.0"), MaterialHardness);
MaterialHardness = 50.0f; // Default material hardness
}
// Hardness ratio affects penetration and ricochet
return FMath::Max(0.1f, BulletHardness / MaterialHardness);
float HardnessRatio = BulletHardness / MaterialHardness;
// SANITY CHECK: Ensure reasonable bounds
return FMath::Clamp(HardnessRatio, 0.1f, 10.0f);
}
float UEBMathematicalBallistics::CalculateVelocityFactor(float Velocity, float ThresholdVelocity)
{
// SANITY CHECK: Validate input values
if (!FMath::IsFinite(Velocity) || Velocity < 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid velocity %.3f for velocity factor"), Velocity);
return 0.1f; // Minimum factor
}
if (!FMath::IsFinite(ThresholdVelocity) || ThresholdVelocity <= 0.0f)
{
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid threshold velocity %.3f, using default 300.0"), ThresholdVelocity);
ThresholdVelocity = 300.0f; // Default threshold
}
// Velocity factor for various calculations
return FMath::Clamp(Velocity / ThresholdVelocity, 0.1f, 5.0f);
float VelocityFactor = Velocity / ThresholdVelocity;
return FMath::Clamp(VelocityFactor, 0.1f, 5.0f);
}
float UEBMathematicalBallistics::CalculateAngleFactor(float ImpactAngleDegrees)
+16 -3
View File
@@ -22,13 +22,26 @@ float AEBBullet::GetAirDensity_Implementation(UWorld* World, FVector Location) c
}
float AEBBullet::GetSpeedOfSound_Implementation(UWorld* World, FVector Location) const{
// Safety check: Ensure WorldScale is valid
float SafeWorldScale = FMath::Max(WorldScale, 0.01f);
if (!SpeedOfSoundVariesWithAltitude) {
return SeaLevelSpeedOfSound * WorldScale;
return FMath::Max(SeaLevelSpeedOfSound * SafeWorldScale, 1.0f);
}
float Altitude = GetAltitude(World, Location);
float soundvmp = SeaLevelSpeedOfSound / GetCurveValue(SpeedOfSoundCurve, 0, SeaLevelSpeedOfSound);
return GetCurveValue(SpeedOfSoundCurve, Altitude, SeaLevelSpeedOfSound)*WorldScale*soundvmp;
float CurveValueAtSeaLevel = GetCurveValue(SpeedOfSoundCurve, 0, SeaLevelSpeedOfSound);
// Safety check: Prevent division by zero
if (FMath::IsNearlyZero(CurveValueAtSeaLevel, 0.01f)) {
return FMath::Max(SeaLevelSpeedOfSound * SafeWorldScale, 1.0f);
}
float soundvmp = SeaLevelSpeedOfSound / CurveValueAtSeaLevel;
float result = GetCurveValue(SpeedOfSoundCurve, Altitude, SeaLevelSpeedOfSound) * SafeWorldScale * soundvmp;
// Safety check: Ensure result is never zero or negative
return FMath::Max(result, 1.0f);
}
+28 -10
View File
@@ -76,7 +76,11 @@ AEBBullet* AEBBullet::SpawnOrReactivate(UWorld* World, TSubclassOf<class AEBBull
Recycled->IgnoredActors = Default->IgnoredActors;
Recycled->SafeDelay = Default->SafeDelay;
Recycled->SetLifeSpan(Default->InitialLifeSpan);
Recycled->FinishSpawning(Transform);
AEBBullet* FinishedBullet = Recycled->FinishSpawning(Transform);
if (!FinishedBullet) {
// If spawning failed, return nullptr
return nullptr;
}
//if (!Recycled->HasActorBegunPlay()){ Recycled->BeginPlay(); }
//Recycled->ReactivationBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(Recycled->GetWorld(), Transform.GetLocation()), BulletVelocity, BulletOwner, BulletInstigator);
#ifdef WITH_EDITOR
@@ -84,20 +88,24 @@ AEBBullet* AEBBullet::SpawnOrReactivate(UWorld* World, TSubclassOf<class AEBBull
GEngine->AddOnScreenDebugMessage(0, 2, FColor::Green, TEXT("Recycling pooled bullet"));
}
#endif
return Recycled;
return FinishedBullet;
}
else {
bullet = Cast<AEBBullet>(World->SpawnActorDeferred<AEBBullet>(BulletClass, Transform, BulletOwner, BulletInstigator));
bullet->RandomStream.GenerateNewSeed();
bullet->Velocity = BulletVelocity;
bullet->FinishSpawning(Transform);
AEBBullet* FinishedBullet = bullet->FinishSpawning(Transform);
if (!FinishedBullet) {
// If spawning failed, return nullptr
return nullptr;
}
//UGameplayStatics::FinishSpawningActor(bullet, Transform);
#ifdef WITH_EDITOR
if (bullet->DebugPooling) {
GEngine->AddOnScreenDebugMessage(0, 2, FColor::Orange, TEXT("Spawning new bullet"));
}
#endif
return bullet;
return FinishedBullet;
}
}
@@ -122,7 +130,11 @@ AEBBullet* AEBBullet::SpawnOrReactivateFromBarrel(UWorld* World, TSubclassOf<cla
Recycled->IgnoredActors = Default->IgnoredActors;
Recycled->SafeDelay = Default->SafeDelay;
Recycled->SetLifeSpan(Default->InitialLifeSpan);
Recycled->FinishSpawning(Transform);
AEBBullet* FinishedBullet = Recycled->FinishSpawning(Transform);
if (!FinishedBullet) {
// If spawning failed, return nullptr
return nullptr;
}
//if (!Recycled->HasActorBegunPlay()){ Recycled->BeginPlay(); }
//Recycled->ReactivationBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(Recycled->GetWorld(), Transform.GetLocation()), BulletVelocity, BulletOwner, BulletInstigator);
#ifdef WITH_EDITOR
@@ -130,32 +142,38 @@ AEBBullet* AEBBullet::SpawnOrReactivateFromBarrel(UWorld* World, TSubclassOf<cla
GEngine->AddOnScreenDebugMessage(0, 2, FColor::Green, TEXT("Recycling pooled bullet"));
}
#endif
return Recycled;
return FinishedBullet;
}
else {
bullet = Cast<AEBBullet>(World->SpawnActorDeferred<AEBBullet>(BulletClass, Transform, BulletOwner, BulletInstigator));
bullet->RandomStream.GenerateNewSeed();
bullet->Velocity = BulletVelocity;
bullet->SetFiringBarrel(SourceBarrel);
bullet->FinishSpawning(Transform);
AEBBullet* FinishedBullet = bullet->FinishSpawning(Transform);
if (!FinishedBullet) {
// If spawning failed, return nullptr
return nullptr;
}
//UGameplayStatics::FinishSpawningActor(bullet, Transform);
#ifdef WITH_EDITOR
if (bullet->DebugPooling) {
GEngine->AddOnScreenDebugMessage(0, 2, FColor::Orange, TEXT("Spawning new bullet"));
}
#endif
return bullet;
return FinishedBullet;
}
}
void AEBBullet::FinishSpawning(FTransform Transform) {
AEBBullet* AEBBullet::FinishSpawning(FTransform Transform) {
if(IsRecycled){
if (!HasActorBegunPlay()){
BeginPlay();
}
ReactivationBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(this->GetWorld(), Transform.GetLocation()), this->Velocity, this->GetOwner(), this->GetInstigator());
return this;
}else{
UGameplayStatics::FinishSpawningActor(this, Transform);
AActor* FinishedActor = UGameplayStatics::FinishSpawningActor(this, Transform);
return Cast<AEBBullet>(FinishedActor);
}
}
+30 -4
View File
@@ -84,7 +84,12 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
}
if (MaterialRestitutionControlsRicochet) {
RicochetRestitution *= PhysMaterial->Restitution;
ricRestitution = FMath::Clamp(ricRestitution * PhysMaterial->Restitution, 0.0f, 0.95f);
if (PhysMaterial->Restitution > 1.5f) {
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Material '%s' has very high restitution %.2f, clamping ricochet restitution to %.2f"),
*PhysMaterial->GetName(), PhysMaterial->Restitution, ricRestitution);
}
}
}
@@ -164,9 +169,15 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
//bounce
FVector bounceAngle = flat * dot * (1.0f - ricFriction);
bounceAngle += HitResult.Normal * (1.0f - dot) * ricRestitution;
bounceAngle = RandomStream.VRandCone(bounceAngle, ricSpread) * bounceAngle.Size();
NewVelocity = bounceAngle * Velocity.Size();
// CRITICAL FIX: Normalize bounceAngle before using with VRandCone to prevent infinite velocity
FVector normalizedBounceAngle = bounceAngle.GetSafeNormal();
FVector ricochetDirection = RandomStream.VRandCone(normalizedBounceAngle, ricSpread);
// Apply proper velocity scaling with safety clamps
float ricochetSpeed = Velocity.Size() * FMath::Clamp(ricRestitution, 0.0f, 1.0f);
NewVelocity = ricochetDirection * ricochetSpeed;
Ricochet = true;
OwnerSafe = false;
}
@@ -200,6 +211,12 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
Penetration = true;
OwnerSafe = false;
// X-ray trajectory debug visualization
if (DebugEnabled && DebugTrajectory && ShowXRayTrajectory)
{
DrawXRayTrajectory(HitResult.Location, exitLoc, PenetrationVector, PenetrationDepth, PhysMaterial);
}
// CRITICAL FIX: Ensure trace continues after penetration
// Calculate how much of the original trace distance was used for penetration
float PenetrationTraceDistance = (exitLoc - HitResult.Location).Size();
@@ -305,6 +322,15 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
}
}
// SAFETY: Clamp velocity to prevent infinite speeds and numerical instability
float MaxSafeVelocity = MuzzleVelocityMax * 10.0f; // Allow up to 10x muzzle velocity as safety limit
if (!NewVelocity.IsNearlyZero() && NewVelocity.Size() > MaxSafeVelocity)
{
NewVelocity = NewVelocity.GetSafeNormal() * MaxSafeVelocity;
UE_LOG(LogTemp, Warning, TEXT("EBBullet: Velocity clamped from %.1f to %.1f to prevent infinite speed (Ricochet=%s, Penetration=%s)"),
Velocity.Size(), NewVelocity.Size(), Ricochet ? TEXT("true") : TEXT("false"), Penetration ? TEXT("true") : TEXT("false"));
}
Velocity = NewVelocity;
if ((Velocity.Size() < DespawnVelocity) || (!Ricochet && !Penetration && (DespawnVelocity>0.0f))){
+7 -1
View File
@@ -59,10 +59,13 @@ public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) bool ShowTrail = true;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) bool ShowVelocityVectors = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) bool ShowPathPrediction = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) bool ShowXRayTrajectory = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) float DebugTrailTime = 1.0f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) float DebugTrailWidth = 0.0f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) FLinearColor DebugTrailColorFast = FLinearColor(0, 1, 0, 1);
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) FLinearColor DebugTrailColorSlow = FLinearColor(1, 0, 0, 1);
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) FLinearColor XRayTrajectoryColor = FLinearColor(0, 1, 1, 0.7f);
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) FLinearColor PenetrationTrajectoryColor = FLinearColor(1, 0.5f, 0, 0.8f);
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) float VelocityVectorScale = 0.01f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) int32 PathPredictionSteps = 10;
@@ -311,6 +314,9 @@ public:
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
void DrawTrajectoryDebug(FVector StartLocation, FVector EndLocation, FVector CurrentVelocity, float DeltaTime);
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
void DrawXRayTrajectory(FVector EntryPoint, FVector ExitPoint, FVector PenetrationDirection, float PenetrationDepth, UPhysicalMaterial* Material);
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
void DrawImpactDebug(FVector ImpactLocation, FVector ImpactVelocity, FVector ImpactNormal, bool bRicochet, bool bPenetration, float PenetrationDepth, UPhysicalMaterial* Material);
@@ -367,7 +373,7 @@ private:
static AEBBullet* SpawnOrReactivateFromBarrel(UWorld* World, TSubclassOf<class AEBBullet> BulletClass, const FTransform& Transform, FVector BulletVelocity, AActor* BulletOwner, APawn* BulletInstigator, class UEBBarrel* SourceBarrel);
void DeactivateToPool();
void FinishSpawning(FTransform Transform);
AEBBullet* FinishSpawning(FTransform Transform);
void Step(float DeltaTime);
+368
View File
@@ -0,0 +1,368 @@
# Debug Commands Reference
EasyBallistics provides a comprehensive set of debug commands and visualization tools to help developers troubleshoot and optimize their ballistic systems.
## Console Commands
### Core Debug Commands
#### `eb.debug.toggle`
Toggle debug display for all EasyBallistics systems.
```
eb.debug.toggle
```
**Usage**: Enables or disables all debug visualization globally.
#### `eb.debug.category`
Toggle specific debug categories for fine-grained control.
```
eb.debug.category <CategoryName> <0|1>
```
**Parameters**:
- `CategoryName`: The debug category to toggle
- `0|1`: Disable (0) or enable (1) the category
**Available Categories**:
- `Trajectory` - Bullet path visualization
- `Impact` - Impact point and penetration visualization
- `Physics` - Physics forces and atmospheric effects
- `Performance` - Performance metrics and profiling
- `Ballistics` - Mathematical ballistic calculations
- `Spalling` - Secondary fragment generation
- `Pooling` - Object pooling statistics
**Examples**:
```
eb.debug.category Trajectory 1 # Enable trajectory visualization
eb.debug.category Impact 1 # Enable impact visualization
eb.debug.category Physics 0 # Disable physics debug
```
#### `eb.debug.clear`
Clear all debug visualization elements.
```
eb.debug.clear
```
**Usage**: Removes all debug lines, text, and visualization elements from the screen.
#### `eb.debug.info`
Display comprehensive debug information for all active bullets.
```
eb.debug.info
```
**Usage**: Shows detailed information about bullet states, physics calculations, and system performance.
### Editor-Only Commands
#### `EasyBallistics.OpenImportExportTool`
Open the JSON Import/Export tool window.
```
EasyBallistics.OpenImportExportTool
```
**Usage**: Opens the dedicated tool for importing and exporting ballistic data as JSON files.
## Debug Categories
### Trajectory Debug
Visualizes bullet flight paths and trajectory information.
**Features**:
- **Trail Visualization**: Shows bullet path with color-coded velocity
- **Velocity Vectors**: Displays velocity direction and magnitude
- **Path Prediction**: Shows predicted trajectory
- **Atmospheric Effects**: Visualizes wind and air density impacts
**Configuration**:
```cpp
// In bullet configuration
Bullet->DebugTrajectory = true;
Bullet->ShowTrail = true;
Bullet->ShowVelocityVectors = true;
Bullet->ShowPathPrediction = true;
```
### Impact Debug
Shows impact points, penetration depth, and material interactions.
**Features**:
- **Impact Points**: Marks where bullets hit surfaces
- **Penetration Depth**: Shows how deep bullets penetrate
- **Ricochet Angles**: Displays ricochet probability and angles
- **Material Information**: Shows material properties at impact
- **Surface Normals**: Visualizes surface normal vectors
**Configuration**:
```cpp
// In bullet configuration
Bullet->DebugImpact = true;
Bullet->ShowImpactPoints = true;
Bullet->ShowPenetrationDepth = true;
Bullet->ShowRicochetAngles = true;
Bullet->ShowMaterialInfo = true;
```
### Physics Debug
Visualizes physics forces and atmospheric effects.
**Features**:
- **Drag Forces**: Shows air resistance effects
- **Gravity Visualization**: Displays gravitational effects
- **Wind Effects**: Shows wind force vectors
- **Atmospheric Data**: Displays air density and temperature
**Configuration**:
```cpp
// In bullet configuration
Bullet->DebugPhysics = true;
Bullet->ShowDragForces = true;
Bullet->ShowGravityEffect = true;
Bullet->ShowWindEffect = true;
Bullet->ShowAtmosphericData = true;
```
### Performance Debug
Shows performance metrics and profiling information.
**Features**:
- **Trace Count**: Number of traces per frame
- **Frame Timing**: Performance impact measurements
- **Pooling Statistics**: Object pooling efficiency
- **Memory Usage**: Memory allocation tracking
**Configuration**:
```cpp
// In bullet configuration
Bullet->DebugPerformance = true;
Bullet->ShowTraceCount = true;
Bullet->ShowFrameTiming = true;
Bullet->ShowPoolingStats = true;
```
### Ballistics Debug
Displays mathematical ballistic calculations.
**Features**:
- **Energy Calculations**: Kinetic energy and momentum
- **Mathematical Comparison**: Artistic vs mathematical mode comparison
- **Ballistic Coefficient**: Real-time BC calculations
- **Penetration Formulas**: Shows mathematical penetration results
**Configuration**:
```cpp
// In bullet configuration
Bullet->DebugBallistics = true;
Bullet->ShowEnergyCalculations = true;
Bullet->ShowMathematicalComparison = true;
Bullet->ShowBallisticCoefficient = true;
```
### Spalling Debug
Visualizes secondary fragment generation.
**Features**:
- **Fragment Generation**: Shows where spalling occurs
- **Fragment Trajectories**: Displays fragment paths
- **Spall Cones**: Shows fragment dispersion patterns
- **Energy Distribution**: Visualizes energy transfer to fragments
**Configuration**:
```cpp
// In bullet configuration
Bullet->DebugSpalling = true;
Bullet->ShowSpallGeneration = true;
Bullet->ShowFragmentTrajectories = true;
Bullet->ShowSpallCones = true;
```
## Debug Configuration
### Blueprint Configuration
Debug settings can be configured through Blueprint:
```cpp
// Get bullet reference
AEBBullet* Bullet = GetBullet();
// Enable debug categories
Bullet->SetBulletDebugCategory("Trajectory", true);
Bullet->SetBulletDebugCategory("Impact", true);
// Configure visualization
Bullet->DebugTrailTime = 2.0f;
Bullet->ImpactDebugTime = 5.0f;
Bullet->WorldTextScale = 1.2f;
```
### Barrel-Level Debug Control
The barrel component provides debug control for all bullets it fires:
```cpp
// Get barrel reference
UEBBarrel* Barrel = GetBarrel();
// Set debug categories for all bullets
Barrel->SetBulletDebugCategory("Trajectory", true);
Barrel->SetAllBulletDebugCategories(true);
// Clear debug display
Barrel->ClearAllBulletDebugDisplay();
```
## Visualization Options
### Color Coding
Debug visualizations use color coding to convey information:
**Trajectory Colors**:
- **Green**: High velocity (fast bullets)
- **Yellow**: Medium velocity
- **Red**: Low velocity (slow bullets)
**Impact Colors**:
- **Green**: Successful penetration
- **Yellow**: Ricochet
- **Red**: Stopped/blocked impact
**Physics Colors**:
- **Blue**: Drag forces
- **Purple**: Gravity forces
- **Cyan**: Wind forces
### Text Display Options
Debug text can be displayed in world space or as on-screen messages:
```cpp
// Configure text display
Bullet->UseWorldSpaceText = true;
Bullet->UseOnScreenMessages = true;
Bullet->WorldTextScale = 1.0f;
Bullet->MaxOnScreenMessages = 10;
```
## Performance Considerations
### Debug Impact on Performance
Debug visualization can impact performance, especially with many active bullets:
- **Trajectory trails**: High impact with many bullets
- **Text rendering**: Moderate impact
- **Line drawing**: Low to moderate impact
- **On-screen messages**: Low impact
### Optimization Tips
1. **Use Specific Categories**: Enable only needed debug categories
2. **Limit Debug Time**: Reduce debug display duration
3. **Batch Operations**: Use barrel-level debug control
4. **Conditional Debugging**: Enable debug only during development
```cpp
// Conditional debug enabling
#if WITH_EDITOR
Bullet->DebugEnabled = true;
#else
Bullet->DebugEnabled = false;
#endif
```
## Troubleshooting Debug Issues
### Common Problems
#### Debug Not Showing
- Check if debug is enabled globally: `eb.debug.toggle`
- Verify specific categories are enabled: `eb.debug.category <name> 1`
- Ensure bullets are configured for debug: `Bullet->DebugEnabled = true`
#### Performance Issues
- Disable expensive debug categories (Trajectory, Spalling)
- Reduce debug display times
- Limit number of active bullets during debug
#### Visual Clutter
- Use `eb.debug.clear` to remove old debug elements
- Enable only essential debug categories
- Reduce text scale and message count
### Debug Information Output
Get comprehensive debug information:
```cpp
// Get debug info string
FString DebugInfo = Bullet->GetDebugInfoString();
UE_LOG(LogTemp, Warning, TEXT("%s"), *DebugInfo);
```
## Advanced Debug Features
### Custom Debug Widgets
Create custom debug widgets for impact visualization:
```cpp
// Create custom impact widget
Bullet->CreateDebugImpactWidget(
ImpactLocation,
bRicochet,
bPenetration,
ImpactVelocity,
PenetrationDepth,
HitMaterial
);
```
### Debug Event Binding
Bind to debug events for custom visualization:
```cpp
// Bind to impact debug event
Bullet->OnImpact.AddDynamic(this, &AMyClass::OnBulletImpact);
UFUNCTION()
void OnBulletImpact(/* impact parameters */)
{
// Custom debug visualization
DrawCustomDebugInfo();
}
```
## Best Practices
1. **Development Only**: Use debug features only during development
2. **Selective Enabling**: Enable only necessary debug categories
3. **Performance Monitoring**: Monitor FPS impact when using debug
4. **Clean Up**: Use `eb.debug.clear` to remove debug clutter
5. **Consistent Settings**: Use barrel-level debug control for consistency
## Integration with Profiling Tools
EasyBallistics debug features integrate with Unreal's profiling tools:
```cpp
// Enable profiling-friendly debug
Bullet->DebugPerformance = true;
// Use with stat commands
// stat EasyBallistics
// stat Memory
// stat RenderThreadCommands
```
This comprehensive debug system provides powerful tools for developing, testing, and optimizing ballistic systems in EasyBallistics.
+442
View File
@@ -0,0 +1,442 @@
# Barrel Component Guide
The `UEBBarrel` component is the heart of EasyBallistics' weapon system, managing all aspects of firing mechanics, ammunition cycling, and weapon behavior. This component provides comprehensive control over how projectiles are launched and how weapons behave.
## Overview
The `UEBBarrel` component serves as the interface between your weapon systems and the ballistic simulation. It handles:
- **Firing Mechanics**: Different fire modes and timing
- **Ammunition Management**: Loading, cycling, and tracking ammunition
- **Weapon Behavior**: Spread, velocity variation, and recoil
- **Networking**: Multiplayer synchronization and prediction
- **Debug Integration**: Comprehensive debug and visualization tools
## Basic Setup
### Adding to Your Weapon
```cpp
// In your weapon class header
UCLASS()
class MYGAME_API AMyWeapon : public AActor
{
GENERATED_BODY()
public:
AMyWeapon();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
class UEBBarrel* Barrel;
};
// In constructor
AMyWeapon::AMyWeapon()
{
Barrel = CreateDefaultSubobject<UEBBarrel>(TEXT("Barrel"));
}
```
### Blueprint Setup
1. **Add Component**: Add `EBBarrel` component to your weapon Blueprint
2. **Position Component**: Place at the muzzle location
3. **Configure Properties**: Set up firing parameters in the Details panel
## Fire Modes
The barrel component supports multiple fire modes through the `EFireMode` enumeration:
### Full Auto (`FM_Auto`)
Continuous firing while trigger is held.
```cpp
// Configure for full auto
Barrel->FireMode = EFireMode::FM_Auto;
Barrel->FireRateMin = 600.0f; // 600 RPM minimum
Barrel->FireRateMax = 650.0f; // 650 RPM maximum
```
### Semi-Auto (`FM_Semiauto`)
One shot per trigger pull.
```cpp
// Configure for semi-auto
Barrel->FireMode = EFireMode::FM_Semiauto;
Barrel->FireRateMax = 300.0f; // Maximum cyclic rate
```
### Burst (`FM_Burst`)
Fixed number of shots per trigger pull.
```cpp
// Configure for burst fire
Barrel->FireMode = EFireMode::FM_Burst;
Barrel->BurstCount = 3; // 3-round burst
Barrel->BurstCooldown = 0.2f; // 200ms between bursts
```
### Interruptible Burst (`FM_InterBurst`)
Burst fire that can be interrupted by releasing the trigger.
```cpp
// Configure for interruptible burst
Barrel->FireMode = EFireMode::FM_InterBurst;
Barrel->BurstCount = 5; // Up to 5 rounds
Barrel->BurstCooldown = 0.1f; // 100ms between bursts
```
### Manual (`FM_Manual`)
Manual firing (bolt-action style).
```cpp
// Configure for manual operation
Barrel->FireMode = EFireMode::FM_Manual;
// Requires manual charging between shots
```
### Slam Fire (`FM_Slamfire`)
Fires immediately when ammunition is chambered.
```cpp
// Configure for slam fire
Barrel->FireMode = EFireMode::FM_Slamfire;
Barrel->FireRateMax = 120.0f; // Limited by loading speed
```
### Gatling (`FM_Gatling`)
Variable fire rate with spool-up and spool-down mechanics.
```cpp
// Configure for gatling operation
Barrel->FireMode = EFireMode::FM_Gatling;
Barrel->GatlingAutoSpool = true;
Barrel->GatlingSpoolUpTime = 2.0f; // 2 seconds to full speed
Barrel->GatlingSpoolDownTime = 1.5f; // 1.5 seconds to stop
Barrel->FireRateMin = 0.0f; // Stopped
Barrel->FireRateMax = 3000.0f; // 3000 RPM at full speed
```
## Ammunition System
### Basic Ammunition Setup
```cpp
// Set up ammunition array
TArray<TSubclassOf<AEBBullet>> AmmoTypes;
AmmoTypes.Add(MyBulletClass);
Barrel->SetAmmo(100, false, false, true, AmmoTypes);
```
### Ammunition Cycling
Enable automatic ammunition cycling:
```cpp
// Enable cycling with unlimited ammo
Barrel->CycleAmmo = true;
Barrel->CycleAmmoUnlimited = true;
Barrel->CycleAmmoCount = 30; // 30 rounds before cycling
```
### Manual Ammunition Management
```cpp
// Manually load ammunition
Barrel->Charge();
// Unload chambered round
Barrel->UnloadChambered(true);
// Check ammunition status
int32 AmmoCount = Barrel->GetAmmoCount(true); // Include chambered round
TArray<TSubclassOf<AEBBullet>> CurrentAmmo = Barrel->GetAmmo(true);
```
## Weapon Characteristics
### Spread and Accuracy
```cpp
// Configure weapon spread
Barrel->Spread = 0.01f; // 0.01 radians (~0.6 degrees)
Barrel->SpreadBias = 2.0f; // Higher bias = more accurate on average
// Configure muzzle velocity variation
Barrel->MuzzleVelocityMultiplierMin = 0.95f; // 95% minimum velocity
Barrel->MuzzleVelocityMultiplierMax = 1.05f; // 105% maximum velocity
```
### Recoil and Physics
```cpp
// Configure recoil effects
Barrel->RecoilMultiplier = 1.0f; // Full recoil
Barrel->InheritVelocity = 1.0f; // Full velocity inheritance
Barrel->AdditionalVelocity = FVector(0, 0, 0); // No additional velocity
```
## Advanced Features
### Gatling Mechanics
For weapons with variable fire rates:
```cpp
// Manual gatling control
Barrel->GatlingSpool(true); // Start spooling up
Barrel->GatlingSpool(false); // Start spooling down
// Check gatling status
bool bIsSpooling = Barrel->Spooling;
float SpoolPhase = Barrel->GatlingPhase; // 0.0 to 1.0
float CurrentRPS = Barrel->GatlingRPS; // Current rounds per second
```
### Prediction and Targeting
```cpp
// Predict bullet trajectory
bool bHit;
FHitResult HitResult;
FVector HitLocation;
float HitTime;
AActor* HitActor;
TArray<FVector> Trajectory;
Barrel->PredictHit(
bHit, HitResult, HitLocation, HitTime, HitActor, Trajectory,
MyBulletClass, IgnoredActors, 10.0f, 0.1f
);
// Calculate aim direction for moving targets
FVector AimDirection;
FVector PredictedTargetLocation;
FVector PredictedIntersectionLocation;
float PredictedFlightTime;
float Error;
Barrel->CalculateAimDirection(
MyBulletClass, TargetLocation, TargetVelocity,
AimDirection, PredictedTargetLocation, PredictedIntersectionLocation,
PredictedFlightTime, Error
);
```
## Events and Delegates
### Firing Events
```cpp
// Bind to firing events
Barrel->BeforeShotFired.AddDynamic(this, &AMyWeapon::OnBeforeShotFired);
Barrel->ShotFired.AddDynamic(this, &AMyWeapon::OnShotFired);
Barrel->AmmoDepleted.AddDynamic(this, &AMyWeapon::OnAmmoDepleted);
Barrel->ReadyToShoot.AddDynamic(this, &AMyWeapon::OnReadyToShoot);
UFUNCTION()
void OnBeforeShotFired()
{
// Called just before bullet is spawned
// Good for muzzle flash effects
}
UFUNCTION()
void OnShotFired()
{
// Called after bullet is spawned
// Good for sound effects and recoil
}
UFUNCTION()
void OnAmmoDepleted()
{
// Called when ammunition runs out
// Good for reload prompts
}
UFUNCTION()
void OnReadyToShoot()
{
// Called when weapon is ready to fire again
// Good for UI updates
}
```
### Custom Bullet Transformation
```cpp
// Override bullet spawn transform
UFUNCTION(BlueprintNativeEvent)
void InitialBulletTransform(FVector InLocation, FVector InDirection,
FVector& OutLocation, FVector& OutDirection);
void AMyWeapon::InitialBulletTransform_Implementation(
FVector InLocation, FVector InDirection,
FVector& OutLocation, FVector& OutDirection)
{
// Modify bullet spawn location and direction
OutLocation = InLocation + FVector(0, 0, 5); // Slight offset
OutDirection = InDirection;
}
```
### Custom Recoil Implementation
```cpp
// Override recoil application
UFUNCTION(BlueprintNativeEvent)
void ApplyRecoil(UPrimitiveComponent* Component, FVector InLocation, FVector Impulse);
void AMyWeapon::ApplyRecoil_Implementation(
UPrimitiveComponent* Component, FVector InLocation, FVector Impulse)
{
// Apply custom recoil to weapon
if (Component)
{
Component->AddImpulseAtLocation(Impulse, InLocation);
}
}
```
## Networking
### Replication Setup
```cpp
// Configure networking
Barrel->ReplicateVariables = true;
Barrel->ReplicateShotFiredEvents = true;
Barrel->ClientSideAim = true;
Barrel->ClientAimUpdateFrequency = 20.0f;
Barrel->ClientAimDistanceLimit = 1000.0f;
```
### Client-Side Prediction
```cpp
// Enable client-side aiming for responsiveness
Barrel->ClientSideAim = true;
Barrel->ClientAimUpdateFrequency = 15.0f; // 15 updates per second
Barrel->ClientAimDistanceLimit = 200.0f; // 200 units prediction distance
```
## Debug and Visualization
### Debug Configuration
```cpp
// Enable debug visualization
Barrel->DebugArrowSize = 100.0f;
Barrel->DebugImpactInfo = true;
// Control bullet debug settings
Barrel->SetBulletDebugCategory("Trajectory", true);
Barrel->SetBulletDebugCategory("Impact", true);
Barrel->SetAllBulletDebugCategories(false); // Disable all
Barrel->ClearAllBulletDebugDisplay(); // Clear display
```
### Debug Information
```cpp
// Get debug information
FString DebugInfo = Barrel->GetBulletDebugInfo();
UE_LOG(LogTemp, Warning, TEXT("Barrel Debug: %s"), *DebugInfo);
```
## Performance Optimization
### Object Pooling
All bullets fired from the barrel can use object pooling:
```cpp
// Enable pooling on bullet class
MyBulletClass->GetDefaultObject<AEBBullet>()->EnablePooling = true;
MyBulletClass->GetDefaultObject<AEBBullet>()->MaxPoolSize = 100;
```
### Fire Rate Optimization
```cpp
// Optimize fire rate calculations
Barrel->FireRateMin = Barrel->FireRateMax; // Disable randomization
```
## Common Patterns
### Weapon Switching
```cpp
// Switch between fire modes
void SwitchToSemiAuto()
{
Barrel->SwitchFireMode(EFireMode::FM_Semiauto);
}
void SwitchToBurst()
{
Barrel->SwitchFireMode(EFireMode::FM_Burst);
}
```
### Ammunition Types
```cpp
// Set up different ammunition types
TArray<TSubclassOf<AEBBullet>> MixedAmmo;
MixedAmmo.Add(FMJBulletClass); // Full Metal Jacket
MixedAmmo.Add(APBulletClass); // Armor Piercing
MixedAmmo.Add(HPBulletClass); // Hollow Point
Barrel->SetAmmo(90, false, false, true, MixedAmmo);
```
### Weapon Jamming
```cpp
// Simulate weapon jamming
void SimulateJam()
{
Barrel->ShootingBlocked = true;
// Clear jam after delay
FTimerHandle JamTimer;
GetWorld()->GetTimerManager().SetTimer(JamTimer, [this]()
{
Barrel->ShootingBlocked = false;
}, 2.0f, false);
}
```
## Best Practices
1. **Component Positioning**: Always place the barrel component at the muzzle location
2. **Fire Rate Balance**: Use realistic fire rates for your weapon type
3. **Ammunition Management**: Implement proper ammunition tracking and reload systems
4. **Network Optimization**: Use appropriate replication settings for your game type
5. **Debug During Development**: Enable debug features during development, disable for shipping
6. **Performance Monitoring**: Monitor performance with high-rate fire weapons
7. **Event Binding**: Use barrel events for weapon effects and feedback
## Troubleshooting
### Common Issues
#### Weapon Not Firing
- Check if `ShootingBlocked` is false
- Verify ammunition is loaded (`GetAmmoCount() > 0`)
- Ensure fire mode is appropriate for input method
#### Networking Issues
- Enable `ReplicateVariables` for multiplayer
- Check authority when calling `Shoot()`
- Verify client prediction settings
#### Performance Problems
- Reduce fire rate for high-rate weapons
- Enable object pooling
- Limit simultaneous active bullets
This comprehensive barrel component system provides the foundation for creating sophisticated weapon systems in EasyBallistics.
+310
View File
@@ -0,0 +1,310 @@
# Unit Conversions Reference
EasyBallistics uses a mix of metric and imperial units to accommodate different regional preferences and industry standards. This guide covers all unit conversions and provides reference tables for common ballistic calculations.
## Units Overview
### Primary Units in EasyBallistics
| Property | Default Unit | Alternative Units |
|----------|-------------|-------------------|
| **Distance** | Centimeters (cm) | Meters (m), Inches (in), Feet (ft) |
| **Velocity** | Centimeters per second (cm/s) | Meters per second (m/s), Feet per second (fps), Miles per hour (mph) |
| **Mass** | Kilograms (kg) | Grams (g), Grains (gr), Pounds (lb) |
| **Energy** | Joules (J) | Foot-pounds (ft-lbs), Calories (cal) |
| **Pressure** | Millibars (mbar) | Pascals (Pa), PSI (psi), Atmospheres (atm) |
| **Temperature** | Celsius (°C) | Fahrenheit (°F), Kelvin (K) |
| **Angle** | Radians (rad) | Degrees (°), Mils (mil) |
### Unreal Engine Units
- **Unreal Units**: 1 Unreal Unit = 1 centimeter
- **World Scale**: EasyBallistics uses `WorldScale` parameter for unit scaling
## Conversion Functions
### Built-in Mathematical Ballistics Conversions
```cpp
// Velocity conversions
float MPS = UEBMathematicalBallistics::ConvertFPStoMPS(3000.0f); // 3000 fps to m/s
float FPS = UEBMathematicalBallistics::ConvertMPStoFPS(914.4f); // 914.4 m/s to fps
// Energy conversions
float Joules = UEBMathematicalBallistics::ConvertFtLbsToJoules(2500.0f); // 2500 ft-lbs to J
float FtLbs = UEBMathematicalBallistics::ConvertJoulesToFtLbs(3390.0f); // 3390 J to ft-lbs
```
### Custom Conversion Functions
```cpp
// Distance conversions
float ConvertInchesToCM(float Inches) { return Inches * 2.54f; }
float ConvertCMToInches(float CM) { return CM / 2.54f; }
float ConvertMetersToCM(float Meters) { return Meters * 100.0f; }
float ConvertCMToMeters(float CM) { return CM / 100.0f; }
float ConvertFeetToCM(float Feet) { return Feet * 30.48f; }
float ConvertCMToFeet(float CM) { return CM / 30.48f; }
// Velocity conversions
float ConvertFPStoCMPS(float FPS) { return FPS * 30.48f; }
float ConvertCMPStoFPS(float CMPS) { return CMPS / 30.48f; }
float ConvertMPStoCMPS(float MPS) { return MPS * 100.0f; }
float ConvertCMPStoMPS(float CMPS) { return CMPS / 100.0f; }
float ConvertMPHtoCMPS(float MPH) { return MPH * 44.704f; }
float ConvertCMPStoMPH(float CMPS) { return CMPS / 44.704f; }
// Mass conversions
float ConvertGrainsToKg(float Grains) { return Grains * 0.0000647989f; }
float ConvertKgToGrains(float Kg) { return Kg / 0.0000647989f; }
float ConvertGramsToBg(float Grams) { return Grams / 1000.0f; }
float ConvertKgToGrams(float Kg) { return Kg * 1000.0f; }
float ConvertPoundsToKg(float Pounds) { return Pounds * 0.453592f; }
float ConvertKgToPounds(float Kg) { return Kg / 0.453592f; }
// Angle conversions
float ConvertDegreesToRadians(float Degrees) { return Degrees * (PI / 180.0f); }
float ConvertRadiansToDegrees(float Radians) { return Radians * (180.0f / PI); }
float ConvertMilsToRadians(float Mils) { return Mils * 0.000981747f; }
float ConvertRadiansToMils(float Radians) { return Radians / 0.000981747f; }
```
## Conversion Tables
### Velocity Conversions
| From | To | Multiply by |
|------|-----|-------------|
| fps | m/s | 0.3048 |
| fps | cm/s | 30.48 |
| fps | mph | 0.681818 |
| m/s | fps | 3.28084 |
| m/s | cm/s | 100.0 |
| m/s | mph | 2.23694 |
| cm/s | fps | 0.0328084 |
| cm/s | m/s | 0.01 |
| cm/s | mph | 0.0223694 |
### Common Velocity Reference Values
| Velocity | fps | m/s | cm/s |
|----------|-----|-----|------|
| **Subsonic** | < 1,125 | < 343 | < 34,300 |
| **Transonic** | 1,125-1,440 | 343-439 | 34,300-43,900 |
| **Supersonic** | > 1,440 | > 439 | > 43,900 |
| **Typical .22 LR** | 1,200 | 366 | 36,600 |
| **Typical 5.56mm** | 3,000 | 914 | 91,400 |
| **Typical .308 Win** | 2,700 | 823 | 82,300 |
| **Typical .50 BMG** | 2,900 | 884 | 88,400 |
### Mass Conversions
| From | To | Multiply by |
|------|-----|-------------|
| grains | kg | 0.0000647989 |
| grains | g | 0.0647989 |
| kg | grains | 15,432.36 |
| kg | g | 1,000.0 |
| g | grains | 15.43236 |
| g | kg | 0.001 |
| lb | kg | 0.453592 |
| lb | g | 453.592 |
### Common Bullet Weight Reference
| Bullet Weight | Grains | Grams | Kg |
|---------------|--------|-------|-----|
| **Light .22 LR** | 40 | 2.59 | 0.00259 |
| **Heavy .22 LR** | 60 | 3.89 | 0.00389 |
| **Light 5.56mm** | 55 | 3.56 | 0.00356 |
| **Heavy 5.56mm** | 77 | 4.99 | 0.00499 |
| **Light .308** | 150 | 9.72 | 0.00972 |
| **Heavy .308** | 180 | 11.66 | 0.01166 |
| **Light .50 BMG** | 650 | 42.12 | 0.04212 |
| **Heavy .50 BMG** | 800 | 51.84 | 0.05184 |
### Energy Conversions
| From | To | Multiply by |
|------|-----|-------------|
| ft-lbs | J | 1.35582 |
| ft-lbs | cal | 0.323832 |
| J | ft-lbs | 0.737562 |
| J | cal | 0.238846 |
| cal | ft-lbs | 3.08596 |
| cal | J | 4.184 |
### Common Energy Reference Values
| Energy Level | ft-lbs | Joules |
|--------------|--------|--------|
| **Minimum for hunting** | 1,000 | 1,356 |
| **Typical .22 LR** | 140 | 190 |
| **Typical 5.56mm** | 1,300 | 1,763 |
| **Typical .308 Win** | 2,500 | 3,390 |
| **Typical .50 BMG** | 13,000 | 17,626 |
### Distance Conversions
| From | To | Multiply by |
|------|-----|-------------|
| inches | cm | 2.54 |
| inches | m | 0.0254 |
| feet | cm | 30.48 |
| feet | m | 0.3048 |
| cm | inches | 0.393701 |
| cm | feet | 0.0328084 |
| m | cm | 100.0 |
| m | feet | 3.28084 |
### Pressure Conversions
| From | To | Multiply by |
|------|-----|-------------|
| mbar | Pa | 100.0 |
| mbar | psi | 0.0145038 |
| mbar | atm | 0.000986923 |
| Pa | mbar | 0.01 |
| Pa | psi | 0.000145038 |
| psi | mbar | 68.9476 |
| psi | Pa | 6,894.76 |
| atm | mbar | 1,013.25 |
| atm | psi | 14.6959 |
### Temperature Conversions
| From | To | Formula |
|------|-----|---------|
| °C | °F | (°C × 9/5) + 32 |
| °C | K | °C + 273.15 |
| °F | °C | (°F - 32) × 5/9 |
| °F | K | (°F - 32) × 5/9 + 273.15 |
| K | °C | K - 273.15 |
| K | °F | (K - 273.15) × 9/5 + 32 |
## Practical Examples
### Setting Up Bullet Properties
```cpp
// Creating a 5.56mm NATO bullet
FMathematicalBulletProperties BulletProps;
// Using imperial measurements (common in US)
BulletProps.GrainWeight = 62.0f; // 62 grains
BulletProps.DiameterInches = 0.224f; // 0.224 inches
BulletProps.LengthInches = 0.825f; // 0.825 inches
BulletProps.BallisticCoefficientG1 = 0.310f; // G1 BC
// EasyBallistics handles internal conversions automatically
float MassKg = BulletProps.GetMassKg(); // Converts to kg
float DiameterCm = BulletProps.GetDiameterCm(); // Converts to cm
```
### Atmospheric Conditions
```cpp
// Setting up atmospheric conditions
Bullet->SeaLevelAirDensity = 1.225f; // kg/m³ (standard)
Bullet->SeaLevelAirPressure = 1013.25f; // mbar (standard)
Bullet->SeaLevelAirTemperature = 15.0f; // °C (standard)
// Converting from imperial if needed
float TempF = 59.0f; // 59°F
float TempC = (TempF - 32.0f) * 5.0f / 9.0f; // Convert to Celsius
Bullet->SeaLevelAirTemperature = TempC;
```
### Ballistic Calculations
```cpp
// Calculate penetration using metric inputs
float VelocityMPS = 800.0f; // 800 m/s
float ImpactAngle = 15.0f; // 15 degrees
// Convert to EasyBallistics units
float VelocityCMPS = VelocityMPS * 100.0f; // Convert to cm/s
float ImpactAngleRad = ImpactAngle * PI / 180.0f; // Convert to radians
// Use in calculations
float PenetrationDepth = UEBMathematicalBallistics::CalculatePenetrationDepth(
BulletProps, MaterialProps, VelocityCMPS, ImpactAngle
);
```
## Best Practices
### Consistent Units
1. **Internal Consistency**: Use EasyBallistics' native units (cm, cm/s, kg) for all internal calculations
2. **User Interface**: Convert to user-preferred units only at the UI level
3. **Documentation**: Always specify units in comments and documentation
### Conversion Patterns
```cpp
// Good: Clear conversion with comments
float VelocityFPS = 2800.0f; // User input in fps
float VelocityCMPS = VelocityFPS * 30.48f; // Convert to cm/s for EasyBallistics
// Good: Use built-in conversion functions
float VelocityMPS = UEBMathematicalBallistics::ConvertFPStoMPS(VelocityFPS);
float VelocityCMPS = VelocityMPS * 100.0f;
// Avoid: Mixed units without clear conversion
float BadVelocity = 2800.0f; // Unclear units
```
### Regional Considerations
```cpp
// Create region-specific presets
struct FAmericanUnits
{
static float ConvertVelocity(float FPS) { return FPS * 30.48f; }
static float ConvertMass(float Grains) { return Grains * 0.0000647989f; }
static float ConvertDistance(float Inches) { return Inches * 2.54f; }
};
struct FMetricUnits
{
static float ConvertVelocity(float MPS) { return MPS * 100.0f; }
static float ConvertMass(float Grams) { return Grams / 1000.0f; }
static float ConvertDistance(float CM) { return CM; }
};
```
## Troubleshooting Unit Issues
### Common Problems
1. **Incorrect Velocities**: Check if you're using fps vs m/s vs cm/s
2. **Wrong Penetration**: Verify angle units (degrees vs radians)
3. **Mass Confusion**: Ensure grains vs grams vs kilograms consistency
4. **Distance Errors**: Confirm centimeters vs meters vs inches
### Debug Unit Verification
```cpp
// Add unit verification in debug builds
#if WITH_EDITOR
void VerifyUnits()
{
// Test velocity conversion
float TestFPS = 3000.0f;
float TestMPS = UEBMathematicalBallistics::ConvertFPStoMPS(TestFPS);
float TestCMPS = TestMPS * 100.0f;
UE_LOG(LogTemp, Warning, TEXT("Velocity: %f fps = %f m/s = %f cm/s"),
TestFPS, TestMPS, TestCMPS);
// Test mass conversion
float TestGrains = 55.0f;
float TestKg = TestGrains * 0.0000647989f;
UE_LOG(LogTemp, Warning, TEXT("Mass: %f grains = %f kg"), TestGrains, TestKg);
}
#endif
```
This comprehensive unit conversion system ensures accurate ballistic calculations while accommodating different regional preferences and industry standards.
@@ -0,0 +1,441 @@
# Blueprint Integration Guide
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.
## Prerequisites
- Basic knowledge of Unreal Engine Blueprints
- Understanding of Actor Components and Data Assets
- Familiarity with Unreal's event system
## Quick Start: Creating Your First Ballistic Weapon
### Step 1: Create Weapon Blueprint
1. **Create New Blueprint**: Right-click in Content Browser → Blueprint Class → Actor
2. **Name**: `BP_MyWeapon`
3. **Add Components**:
- `Static Mesh Component` (for weapon visual)
- `EBBarrel Component` (for ballistic system)
### Step 2: Configure Barrel Component
In your weapon Blueprint's Event Graph:
```blueprintscript
Event BeginPlay
├── Get EBBarrel Component
├── Set Fire Mode (Full Auto)
├── Set Fire Rate Min (600.0)
├── Set Fire Rate Max (650.0)
└── Set Ammo (Count: 100, Bullet Class: BP_MyBullet)
```
### Step 3: Create Bullet Properties
1. **Right-click** in Content Browser
2. **Ballistics****Bullet Properties**
3. **Name**: `BP_MyBulletProperties`
4. **Configure**: Set grain weight, diameter, ballistic coefficient
### Step 4: Create Bullet Blueprint
1. **Create New Blueprint**: Blueprint Class → EBBullet
2. **Name**: `BP_MyBullet`
3. **Set Bullet Properties Asset**: Reference your `BP_MyBulletProperties`
### Step 5: Add Shooting Logic
In your weapon Blueprint:
```blueprintscript
Input Action Fire
├── Branch (Is Valid: EBBarrel)
│ └── True: EBBarrel → Shoot (Trigger: True)
└── False: Print String "No barrel component!"
```
## Advanced Blueprint Patterns
### Smart Weapon System
Create a flexible weapon system that adapts to different situations:
```blueprintscript
// Weapon Controller Blueprint
Event BeginPlay
├── Create Dynamic Material Instance
├── Bind Events
│ ├── Barrel → On Shot Fired → Play Muzzle Flash
│ ├── Barrel → On Ammo Depleted → Show Reload UI
│ └── Barrel → On Ready To Shoot → Hide Reload UI
└── Initialize Weapon Stats
```
### Ammunition Management
```blueprintscript
// Reload System
Custom Event: Reload Weapon
├── Branch (Has Ammo in Inventory)
│ ├── True:
│ │ ├── EBBarrel → Set Ammo (New Ammo Array)
│ │ ├── Play Reload Animation
│ │ └── Update UI
│ └── False:
│ ├── Play Empty Sound
│ └── Show "No Ammo" Message
```
### Fire Mode Switching
```blueprintscript
// Fire Mode System
Input Action: Switch Fire Mode
├── Switch on Enum (Current Fire Mode)
│ ├── Auto: EBBarrel → Switch Fire Mode (Semi Auto)
│ ├── Semi: EBBarrel → Switch Fire Mode (Burst)
│ └── Burst: EBBarrel → Switch Fire Mode (Auto)
├── Update Fire Mode UI
└── Play Switch Sound
```
## Blueprint Events and Delegates
### Barrel Component Events
```blueprintscript
// Bind to Barrel Events
Event BeginPlay
├── EBBarrel → Bind Event to On Before Shot Fired
├── EBBarrel → Bind Event to On Shot Fired
├── EBBarrel → Bind Event to On Ammo Depleted
└── EBBarrel → Bind Event to On Ready To Shoot
// Event Handlers
On Before Shot Fired
├── Spawn Muzzle Flash
├── Apply Screen Shake
└── Play Fire Sound
On Shot Fired
├── Add Recoil to Camera
├── Update Ammo Counter
└── Spawn Shell Casing
On Ammo Depleted
├── Show Reload Prompt
├── Play Empty Sound
└── Block Further Shooting
On Ready To Shoot
├── Hide Reload Prompt
└── Enable Shooting Input
```
### Bullet Impact Events
```blueprintscript
// Bullet Impact System
Event BeginPlay (in Bullet Blueprint)
├── Use New Impact System = True
├── Enable Spalling = True
└── Bind Impact Events
// Impact Event Handler
On Impact
├── Branch (Did Penetrate)
│ ├── True: Spawn Penetration Effect
│ └── False: Spawn Impact Effect
├── Branch (Is Ricochet)
│ ├── True: Spawn Ricochet Effect
│ └── False: Continue Normal Impact
└── Apply Damage to Hit Actor
```
## Material Response System
### Creating Material Response Maps
```blueprintscript
// Material Response Setup
Event BeginPlay
├── Create Material Response Map
├── Add Entry (Steel)
│ ├── Penetration Depth Multiplier: 0.5
│ ├── Ricochet Probability: 0.3
│ └── Enable Spalling: True
├── Add Entry (Wood)
│ ├── Penetration Depth Multiplier: 2.0
│ ├── Ricochet Probability: 0.1
│ └── Enable Spalling: False
└── Assign to Bullets
```
### Dynamic Material Properties
```blueprintscript
// Context-Sensitive Material Response
Function: Get Material Properties
├── Input: Physical Material
├── Switch on Physical Material
│ ├── Steel: Return Steel Properties
│ ├── Wood: Return Wood Properties
│ ├── Concrete: Return Concrete Properties
│ └── Default: Return Default Properties
└── Output: Material Properties Structure
```
## Advanced Ballistic Features
### Atmospheric Effects
```blueprintscript
// Dynamic Weather System
Event BeginPlay
├── Get Weather Manager
├── Bind to Weather Changed Event
└── Update Bullet Atmospheric Properties
On Weather Changed
├── Get Current Weather Data
├── Calculate Air Density
├── Calculate Temperature
├── Calculate Pressure
└── Update All Active Bullets
```
### Predictive Targeting
```blueprintscript
// Lead Target Calculation
Function: Calculate Lead Target
├── Input: Target Actor, Target Velocity
├── EBBarrel → Calculate Aim Direction
│ ├── Bullet Class: Current Bullet
│ ├── Target Location: Target Position
│ ├── Target Velocity: Target Movement
│ └── Max Time: 10.0
├── Output: Aim Direction, Predicted Hit Location
└── Update Crosshair Position
```
### Ballistic Trajectory Visualization
```blueprintscript
// Trajectory Preview
Function: Show Trajectory
├── Input: Start Location, Aim Direction
├── EBBarrel → Predict Hit
│ ├── Bullet Class: Current Bullet
│ ├── Max Time: 5.0
│ └── Step Size: 0.1
├── For Each (Trajectory Point)
│ └── Spawn Trajectory Marker
└── Output: Trajectory Spline
```
## Debugging in Blueprints
### Debug Visualization
```blueprintscript
// Debug Controls
Input Action: Toggle Debug
├── Branch (Debug Enabled)
│ ├── True:
│ │ ├── EBBarrel → Set All Bullet Debug Categories (False)
│ │ └── Set Debug Enabled (False)
│ └── False:
│ ├── EBBarrel → Set Bullet Debug Category ("Trajectory", True)
│ ├── EBBarrel → Set Bullet Debug Category ("Impact", True)
│ └── Set Debug Enabled (True)
```
### Performance Monitoring
```blueprintscript
// Performance Tracker
Event Tick
├── Branch (Debug Mode)
│ └── True:
│ ├── Get Active Bullet Count
│ ├── Get Frame Rate
│ ├── Update Performance UI
│ └── Check Performance Warnings
```
## UI Integration
### Weapon Status Display
```blueprintscript
// Weapon UI Controller
Event BeginPlay
├── Get Weapon Reference
├── Bind to Weapon Events
└── Initialize UI Elements
Update Ammo Display
├── Get Ammo Count (Include Chambered)
├── Get Total Ammo Available
├── Update Ammo Text
└── Update Ammo Bar
Update Fire Mode Display
├── Get Current Fire Mode
├── Switch on Fire Mode
│ ├── Auto: Set Icon (Auto)
│ ├── Semi: Set Icon (Semi)
│ └── Burst: Set Icon (Burst)
└── Update Fire Mode Text
```
### Crosshair System
```blueprintscript
// Dynamic Crosshair
Event Tick
├── Get Weapon Spread
├── Calculate Crosshair Size
├── Get Predicted Hit Location
├── Update Crosshair Position
└── Update Crosshair Color (Hit/Miss)
```
## Multiplayer Considerations
### Network Replication
```blueprintscript
// Multiplayer Weapon Setup
Event BeginPlay
├── Branch (Has Authority)
│ ├── True: Setup Server Logic
│ └── False: Setup Client Logic
├── EBBarrel → Set Replicate Variables (True)
├── EBBarrel → Set Replicate Shot Fired Events (True)
└── EBBarrel → Set Client Side Aim (True)
```
### Client Prediction
```blueprintscript
// Client-Side Prediction
Custom Event: Client Fire Weapon
├── Branch (Has Authority)
│ ├── True: Fire Immediately
│ └── False:
│ ├── Fire Predicted Shot
│ └── Send Fire Request to Server
```
## Performance Optimization
### Object Pooling
```blueprintscript
// Bullet Pool Manager
Event BeginPlay
├── Create Bullet Pool Array
├── For Loop (Pool Size)
│ └── Add Bullet to Pool
└── Initialize Pool Manager
Get Pooled Bullet
├── Branch (Pool Has Available)
│ ├── True: Return Pooled Bullet
│ └── False: Create New Bullet
└── Configure and Return Bullet
```
### LOD System
```blueprintscript
// Distance-Based LOD
Event Tick
├── Get Distance to Player
├── Switch on Distance Range
│ ├── Close: Full Detail
│ ├── Medium: Reduced Detail
│ └── Far: Minimal Detail
└── Apply LOD Settings
```
## Common Blueprint Patterns
### Weapon Pickup System
```blueprintscript
// Weapon Pickup
On Component Begin Overlap
├── Cast to Player Character
├── Branch (Cast Success)
│ └── True:
│ ├── Add Weapon to Inventory
│ ├── Play Pickup Sound
│ ├── Spawn Pickup Effect
│ └── Destroy Pickup Actor
```
### Weapon Customization
```blueprintscript
// Attachment System
Function: Attach Weapon Mod
├── Input: Mod Type, Mod Data
├── Switch on Mod Type
│ ├── Barrel: Modify Barrel Properties
│ ├── Scope: Modify Accuracy
│ └── Magazine: Modify Ammo Capacity
├── Update Weapon Stats
└── Refresh Weapon Model
```
### Damage System Integration
```blueprintscript
// Damage Application
On Impact (Bullet)
├── Get Hit Actor
├── Calculate Damage
│ ├── Base Damage
│ ├── Velocity Modifier
│ └── Penetration Modifier
├── Apply Damage to Actor
└── Spawn Damage Effects
```
## Best Practices
### Blueprint Organization
1. **Separate Concerns**: Keep weapon logic, UI, and effects in separate Blueprints
2. **Use Interfaces**: Create weapon interfaces for consistent behavior
3. **Event-Driven**: Use events instead of tick-based updates where possible
4. **Performance**: Minimize Blueprint tick functions, use timers instead
### Error Handling
```blueprintscript
// Robust Error Handling
Function: Safe Fire Weapon
├── Branch (Is Valid: Barrel Component)
│ └── True: Continue
│ └── False: Print Error and Return
├── Branch (Has Ammo)
│ └── True: Continue
│ └── False: Play Empty Sound and Return
├── Branch (Not Shooting Blocked)
│ └── True: Fire Weapon
│ └── False: Print "Weapon Blocked" and Return
```
### Testing and Debugging
1. **Use Debug Nodes**: Add debug prints and visualization
2. **Test Multiplayer**: Always test networking features
3. **Profile Performance**: Monitor frame rate with complex setups
4. **Validate Inputs**: Check for null references and invalid data
This comprehensive Blueprint integration guide provides the foundation for creating sophisticated ballistic systems entirely through Blueprints, leveraging the full power of EasyBallistics' Blueprint-friendly architecture.
+4
View File
@@ -29,17 +29,20 @@ const sidebars = {
label: 'Core Concepts',
items: [
'core-concepts/overview',
'core-concepts/barrel-component',
'core-concepts/gatling-mechanics',
'core-concepts/trajectory-prediction',
'core-concepts/mathematical-ballistics',
'core-concepts/ballistic-impact-component',
'core-concepts/spalling-system',
'core-concepts/unit-conversions',
],
},
{
type: 'category',
label: 'Tutorials',
items: [
'tutorials/blueprint-integration',
'tutorials/material-setup-guide',
'tutorials/advanced-weapon-systems',
],
@@ -63,6 +66,7 @@ const sidebars = {
'api/overview',
'api/bullet-reference',
'api/material-properties-reference',
'api/debug-commands-reference',
],
},
'troubleshooting',