init
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
|
||||
Binaries/
|
||||
|
||||
Intermediate/
|
||||
@@ -0,0 +1,85 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is EasyBallistics_C, an Unreal Engine 5.6 plugin that provides realistic ballistics simulation for projectiles. The plugin implements advanced physics calculations including atmospheric effects, material penetration, ricochets, and drag curves.
|
||||
|
||||
## Build System
|
||||
|
||||
This is an Unreal Engine plugin that uses Unreal's build system. The plugin compiles as part of the host project using UnrealBuildTool.
|
||||
|
||||
### Build Configuration
|
||||
- **Module**: EasyBallistics (Runtime module)
|
||||
- **Engine Version**: 5.6.0
|
||||
- **Platforms**: Win64, Linux, Mac, Android
|
||||
- **Build File**: `Source/EasyBallistics/EasyBallistics.Build.cs`
|
||||
|
||||
### Dependencies
|
||||
- **Public**: Core
|
||||
- **Private**: CoreUObject, Engine, PhysicsCore
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **AEBBullet** (`EBBullet.h/.cpp`) - Main bullet actor
|
||||
- Handles physics simulation, atmospheric effects, penetration
|
||||
- Implements pooling system for performance
|
||||
- Supports complex ballistics including drag curves, wind effects
|
||||
- Manages collision detection and material responses
|
||||
|
||||
2. **UEBBarrel** (`EBBarrel.h/.cpp`) - Weapon barrel component
|
||||
- Manages firing mechanics (auto, semi-auto, burst, gatling modes)
|
||||
- Handles ammo cycling and reloading
|
||||
- Implements spread patterns and muzzle velocity variations
|
||||
- Supports networked replication for multiplayer
|
||||
|
||||
3. **UEBMaterialResponseMap** (`EBMaterialResponseMap.h`) - Material system
|
||||
- Maps physical materials to ballistic responses
|
||||
- Configures penetration depth, ricochet probability, friction
|
||||
- Supports different penetration trace types
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Realistic Physics**: Atmospheric density, temperature, pressure effects
|
||||
- **Material Penetration**: Configurable penetration depth per material
|
||||
- **Ricochet System**: Angle-based ricochet probability with energy loss
|
||||
- **Multiplayer Support**: Full replication with client-side prediction
|
||||
- **Performance Optimization**: Object pooling for high-rate fire scenarios
|
||||
- **Shotgun Support**: Multi-projectile spawning with configurable spread
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
Source/EasyBallistics/
|
||||
├── EasyBallistics.Build.cs # Build configuration
|
||||
├── Public/
|
||||
│ ├── EasyBallistics.h # Module header
|
||||
│ ├── EBBarrel.h # Barrel component
|
||||
│ ├── EBBullet.h # Bullet actor
|
||||
│ └── EBMaterialResponseMap.h # Material response system
|
||||
└── Private/
|
||||
├── EasyBallistics.cpp # Module implementation
|
||||
├── EBBarrel.cpp # Barrel implementation
|
||||
├── EBBullet.cpp # Bullet implementation
|
||||
└── [Additional implementation files]
|
||||
```
|
||||
|
||||
## Development Notes
|
||||
|
||||
### Networking
|
||||
- Uses Unreal's replication system extensively
|
||||
- Supports both reliable and unreliable multicast for performance
|
||||
- Client-side aim prediction for responsive gameplay
|
||||
|
||||
### Performance Considerations
|
||||
- Object pooling enabled by default for bullets
|
||||
- Fixed timestep simulation option for consistent physics
|
||||
- Configurable trace complexity and collision margins
|
||||
|
||||
### Physics Integration
|
||||
- Integrates with Unreal's physics system for impulse application
|
||||
- Supports both simple and complex collision detection
|
||||
- Atmospheric model supports Earth-like conditions or custom curves
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 0,
|
||||
"VersionName": "2.83",
|
||||
"FriendlyName": "EasyBallistics_C",
|
||||
"Description": "",
|
||||
"Category": "Gameplay",
|
||||
"CreatedBy": "Mookie",
|
||||
"CreatedByURL": "",
|
||||
"DocsURL": "",
|
||||
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/bbecde0f66914263b57fd2af5a0c7ffe",
|
||||
"SupportURL": "",
|
||||
"EngineVersion": "5.6.0",
|
||||
"CanContainContent": false,
|
||||
"Installed": true,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "EasyBallistics",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Linux",
|
||||
"Mac",
|
||||
"Android"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "EasyBallisticsEditor",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Linux",
|
||||
"Mac"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,38 @@
|
||||
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class EasyBallistics : ModuleRules
|
||||
{
|
||||
public EasyBallistics(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
DefaultBuildSettings = BuildSettingsVersion.Latest;
|
||||
IncludeOrderVersion = EngineIncludeOrderVersion.Latest;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core"
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"PhysicsCore"
|
||||
// ... add private dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBullet.h"
|
||||
|
||||
FVector AEBBullet::UpdateVelocity_Implementation(UWorld* World, FVector Location, FVector PreviousVelocity, float DeltaTime) const {
|
||||
FVector NewVelocity = PreviousVelocity;
|
||||
|
||||
//airDensity
|
||||
float air;
|
||||
float speedOfSound;
|
||||
|
||||
air = GetAirDensity(World, Location);
|
||||
speedOfSound = GetSpeedOfSound(World, Location);
|
||||
|
||||
//gravity
|
||||
if (!OverrideGravity) {
|
||||
NewVelocity += FVector(0, 0, World->GetGravityZ())*DeltaTime;
|
||||
}
|
||||
else {
|
||||
NewVelocity += Gravity*DeltaTime;
|
||||
};
|
||||
|
||||
//drag
|
||||
FVector relVel = (NewVelocity - GetWind(World, Location));
|
||||
float speed = relVel.Size();
|
||||
float mach = speed / speedOfSound;
|
||||
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;
|
||||
|
||||
return NewVelocity;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBarrel.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
#define REPOWNERONLY false
|
||||
|
||||
void UEBBarrel::ShotFiredMulticast_Implementation() {
|
||||
ShotFired.Broadcast();
|
||||
}
|
||||
|
||||
|
||||
void UEBBarrel::Shoot(bool Trigger) {
|
||||
if (ClientSideAim && GetOwner()->GetRemoteRole() == ROLE_Authority && Trigger) {
|
||||
Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
|
||||
Location = GetComponentTransform().GetLocation();
|
||||
ShootRepCSA(Trigger, UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(), Location), Aim);
|
||||
}
|
||||
else {
|
||||
ShootRep(Trigger);
|
||||
}
|
||||
}
|
||||
|
||||
void UEBBarrel::ShootRep_Implementation(bool Trigger) {
|
||||
if (Trigger) {
|
||||
if (FireMode == EFireMode::FM_Burst || FireMode == EFireMode::FM_InterBurst) {
|
||||
BurstRemaining = BurstCount;
|
||||
};
|
||||
Shooting = true;
|
||||
}
|
||||
else {
|
||||
//burst cannot be interrupted
|
||||
if (FireMode != EFireMode::FM_Burst || BurstRemaining<=0) {
|
||||
Shooting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UEBBarrel::ShootRep_Validate(bool Trigger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UEBBarrel::ShootRepCSA_Implementation(bool Trigger, FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) {
|
||||
Location = UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(), NewLocation);
|
||||
Aim = NewAim;
|
||||
RemoteAimReceived = true;
|
||||
|
||||
if (Trigger) {
|
||||
if (FireMode == EFireMode::FM_Burst || FireMode == EFireMode::FM_InterBurst) {
|
||||
BurstRemaining = BurstCount;
|
||||
};
|
||||
Shooting = true;
|
||||
}
|
||||
else {
|
||||
//burst cannot be interrupted
|
||||
if (FireMode != EFireMode::FM_Burst || BurstRemaining <= 0) {
|
||||
Shooting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UEBBarrel::ShootRepCSA_Validate(bool Trigger, FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UEBBarrel::GatlingSpool_Implementation(bool Spool) {
|
||||
Spooling = Spool;
|
||||
}
|
||||
|
||||
bool UEBBarrel::GatlingSpool_Validate(bool Spool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UEBBarrel::SwitchFireMode_Implementation(EFireMode NewFireMode) {
|
||||
FireMode = NewFireMode;
|
||||
}
|
||||
bool UEBBarrel::SwitchFireMode_Validate(EFireMode NewFireMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UEBBarrel::ClientAim_Implementation(FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) {
|
||||
Location = UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(),NewLocation);
|
||||
Aim = NewAim;
|
||||
RemoteAimReceived = true;
|
||||
}
|
||||
bool UEBBarrel::ClientAim_Validate(FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UEBBarrel::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
#if REPOWNERONLY
|
||||
DOREPLIFETIME_CONDITION(UEBBarrel, FireMode, COND_OwnerOnly);
|
||||
DOREPLIFETIME_CONDITION(UEBBarrel, CycleAmmoCount, COND_OwnerOnly);
|
||||
DOREPLIFETIME_CONDITION(UEBBarrel, CycleAmmoPos, COND_OwnerOnly);
|
||||
DOREPLIFETIME_CONDITION(UEBBarrel, Ammo, COND_OwnerOnly);
|
||||
DOREPLIFETIME_CONDITION(UEBBarrel, ChamberedBullet, COND_OwnerOnly);
|
||||
DOREPLIFETIME_CONDITION(UEBBarrel, Shooting, COND_OwnerOnly);
|
||||
DOREPLIFETIME_CONDITION(UEBBarrel, ShootingBlocked, COND_OwnerOnly);
|
||||
DOREPLIFETIME_CONDITION(UEBBarrel, Spooling, COND_OwnerOnly);
|
||||
#else
|
||||
DOREPLIFETIME(UEBBarrel, FireMode);
|
||||
DOREPLIFETIME(UEBBarrel, CycleAmmoCount);
|
||||
DOREPLIFETIME(UEBBarrel, CycleAmmoPos);
|
||||
DOREPLIFETIME(UEBBarrel, Ammo);
|
||||
DOREPLIFETIME(UEBBarrel, ChamberedBullet);
|
||||
DOREPLIFETIME(UEBBarrel, Shooting);
|
||||
DOREPLIFETIME(UEBBarrel, ShootingBlocked);
|
||||
DOREPLIFETIME(UEBBarrel, Spooling);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2019 Mookie. All Rights Reserved.
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "EBBarrel.h"
|
||||
#include "PrimitiveSceneProxy.h"
|
||||
|
||||
FPrimitiveSceneProxy* UEBBarrel::CreateSceneProxy() {
|
||||
{
|
||||
class FBarrelProxy : public FPrimitiveSceneProxy
|
||||
{
|
||||
public:
|
||||
FBarrelProxy(UEBBarrel* InComponent) : FPrimitiveSceneProxy(InComponent)
|
||||
{
|
||||
bWillEverBeLit = false;
|
||||
Component = InComponent;
|
||||
}
|
||||
|
||||
virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
|
||||
{
|
||||
QUICK_SCOPE_CYCLE_COUNTER(STAT_BarrelSceneProxy_GetDynamicMeshElements);
|
||||
|
||||
const FMatrix& Transform = GetLocalToWorld();
|
||||
|
||||
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
||||
{
|
||||
if (VisibilityMap && ((1 << ViewIndex)!=0))
|
||||
{
|
||||
const FSceneView* View = Views[ViewIndex];
|
||||
const FLinearColor DrawColor = GetViewSelectionColor(FColor::Green, *View, IsSelected(), IsHovered(), true, IsIndividuallySelected());
|
||||
|
||||
FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);
|
||||
DrawDirectionalArrow(PDI, Transform, DrawColor, Component->DebugArrowSize, Component->DebugArrowSize*0.1f, 16, Component->DebugArrowSize*0.01f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
|
||||
{
|
||||
const bool bProxyVisible = IsSelected();
|
||||
|
||||
FPrimitiveViewRelevance Result;
|
||||
Result.bDrawRelevance = (IsShown(View));
|
||||
Result.bDynamicRelevance = true;
|
||||
Result.bShadowRelevance = false;
|
||||
Result.bEditorPrimitiveRelevance = UseEditorCompositing(View);
|
||||
return Result;
|
||||
}
|
||||
virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); }
|
||||
uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); }
|
||||
virtual SIZE_T GetTypeHash() const override { return 0; }
|
||||
|
||||
private:
|
||||
UEBBarrel* Component;
|
||||
};
|
||||
|
||||
return new FBarrelProxy(this);
|
||||
}
|
||||
};
|
||||
|
||||
FBoxSphereBounds UEBBarrel::CalcBounds(const FTransform& LocalToWorld) const
|
||||
{
|
||||
float SphereRadius = DebugArrowSize;
|
||||
return FBoxSphereBounds(FVector::ZeroVector, FVector(SphereRadius), SphereRadius).TransformBy(LocalToWorld);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBullet.h"
|
||||
#include "EBMaterialResponseMap.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
void AEBBullet::VelocityChangeBroadcast_Implementation(FVector_NetQuantize NewLocation, FVector NewVelocity) {
|
||||
if (!HasAuthority()) {
|
||||
FVector RebasedLocation = UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(), NewLocation);
|
||||
OnTrajectoryUpdateReceived(RebasedLocation, Velocity, NewVelocity);
|
||||
SetActorLocation(RebasedLocation);
|
||||
Velocity = NewVelocity;
|
||||
CanRetrace = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::VelocityChangeBroadcastReliable_Implementation(FVector_NetQuantize NewLocation, FVector NewVelocity) {
|
||||
if (!HasAuthority()) {
|
||||
FVector RebasedLocation = UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(), NewLocation);
|
||||
OnTrajectoryUpdateReceived(RebasedLocation, Velocity, NewVelocity);
|
||||
SetActorLocation(RebasedLocation);
|
||||
Velocity = NewVelocity;
|
||||
CanRetrace = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::SpawnWithExactVelocity(TSubclassOf<class AEBBullet> BulletClass, AActor* BulletOwner, APawn* BulletInstigator, FVector BulletLocation, FVector BulletVelocity) {
|
||||
|
||||
if (BulletClass != nullptr && BulletOwner != nullptr) {
|
||||
FActorSpawnParameters spawnParams;
|
||||
spawnParams.Owner = BulletOwner;
|
||||
spawnParams.Instigator = BulletInstigator;
|
||||
|
||||
AEBBullet* Default = Cast<AEBBullet>(BulletClass->GetDefaultObject());
|
||||
|
||||
FTransform Transform;
|
||||
Transform.SetLocation(BulletLocation);
|
||||
Transform.SetScale3D(Default->GetActorScale());
|
||||
|
||||
if (Default->RotateActor) {
|
||||
FRotator Rotation = UKismetMathLibrary::MakeRotFromX(BulletVelocity);
|
||||
if (Default->RotateRandomRoll) Rotation.Add(0, 0, Default->RandomStream.FRandRange(-180.0f, 180.0f));
|
||||
Transform.SetRotation(Rotation.Quaternion());
|
||||
}
|
||||
else {
|
||||
Transform.SetRotation(FQuat(1, 0, 0, 1));
|
||||
}
|
||||
|
||||
if (!Default->Shotgun) {
|
||||
AEBBullet* bullet = SpawnOrReactivate(BulletOwner->GetWorld(), BulletClass, Transform, BulletVelocity, BulletOwner, BulletInstigator);
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < Default->ShotCount; i++) {
|
||||
float Vel = BulletVelocity.Size()*Default->RandomStream.FRandRange(1.0 - Default->ShotVelocitySpread, 1.0 + Default->ShotVelocitySpread);
|
||||
FVector SubmunitionVelocity = Default->RandomStream.VRandCone(BulletVelocity, Default->ShotSpread)*Vel;
|
||||
AEBBullet* bullet = SpawnOrReactivate(BulletOwner->GetWorld(), BulletClass, Transform, SubmunitionVelocity, BulletOwner, BulletInstigator);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
UE_LOG(LogTemp, Warning, TEXT("Cannot spawn bullet - invalid class or owner"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AEBBullet::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
DOREPLIFETIME_CONDITION(AEBBullet, Velocity, COND_InitialOnly);
|
||||
DOREPLIFETIME_CONDITION(AEBBullet, RandomStream, COND_InitialOnly);
|
||||
}
|
||||
|
||||
//alternative spawn
|
||||
void AEBBullet::Spawn(TSubclassOf<class AEBBullet> BulletClass, AActor* BulletOwner, APawn* BulletInstigator, FVector BulletLocation, FVector BulletVelocity) {
|
||||
if (BulletClass != nullptr && BulletOwner != nullptr) {
|
||||
|
||||
FActorSpawnParameters spawnParams;
|
||||
spawnParams.Owner = BulletOwner;
|
||||
spawnParams.Instigator = BulletInstigator;
|
||||
|
||||
AEBBullet* Default = Cast<AEBBullet>(BulletClass->GetDefaultObject());
|
||||
|
||||
FTransform Transform;
|
||||
Transform.SetLocation(BulletLocation);
|
||||
Transform.SetScale3D(Default->GetActorScale());
|
||||
|
||||
if (Default->RotateActor) {
|
||||
if (Default->RotateActor) {
|
||||
FRotator Rotation = UKismetMathLibrary::MakeRotFromX(BulletVelocity);
|
||||
if (Default->RotateRandomRoll) Rotation.Add(0, 0, Default->RandomStream.FRandRange(-180.0f, 180.0f));
|
||||
Transform.SetRotation(Rotation.Quaternion());
|
||||
}
|
||||
}
|
||||
else {
|
||||
Transform.SetRotation(FQuat(1, 0, 0, 1));
|
||||
}
|
||||
|
||||
//init velocity
|
||||
float TotalSpread = Default->Spread;
|
||||
if (Default->SpreadBias > 0.0f){
|
||||
float SpreadMult = FMath::Pow(FMath::FRand(), Default->SpreadBias);
|
||||
TotalSpread *= SpreadMult;
|
||||
}
|
||||
|
||||
FVector BulletVelocityNew = Default->RandomStream.VRandCone(BulletVelocity, TotalSpread)*BulletVelocity.Size();
|
||||
float VelocityMP = FMath::Lerp(Default->MuzzleVelocityMin, Default->MuzzleVelocityMax, Default->RandomStream.FRand());
|
||||
BulletVelocityNew = BulletVelocityNew * VelocityMP;
|
||||
|
||||
if (!Default->Shotgun) {
|
||||
AEBBullet* bullet = SpawnOrReactivate(BulletOwner->GetWorld(), BulletClass, Transform, BulletVelocityNew, BulletOwner, BulletInstigator);
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < Default->ShotCount; i++) {
|
||||
float Vel = BulletVelocityNew.Size()*Default->RandomStream.FRandRange(1.0 - Default->ShotVelocitySpread, 1.0 + Default->ShotVelocitySpread);
|
||||
FVector SubmunitionVelocity = Default->RandomStream.VRandCone(BulletVelocityNew, Default->ShotSpread)*Vel;
|
||||
AEBBullet* bullet = SpawnOrReactivate(BulletOwner->GetWorld(), BulletClass, Transform, SubmunitionVelocity, BulletOwner, BulletInstigator);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
UE_LOG(LogTemp, Warning, TEXT("Cannot spawn bullet - invalid class or owner"));
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::OnImpact_Implementation(bool Ricochet, bool PassedThrough, FVector Location, FVector IncomingVelocity, FVector Normal, FVector ExitLocation, FVector ExitVelocity, FVector Impulse, float PenetrationDepth, AActor* Actor, USceneComponent* Component, FName BoneName, UPhysicalMaterial* PhysMaterial, FHitResult HitResult){
|
||||
return;
|
||||
};
|
||||
|
||||
void AEBBullet::OnNetPredictedImpact_Implementation(bool Ricochet, bool PassedThrough, FVector Location, FVector IncomingVelocity, FVector Normal, FVector ExitLocation, FVector ExitVelocity, FVector Impulse, float PenetrationDepth, AActor* Actor, USceneComponent* Component, FName BoneName, UPhysicalMaterial* PhysMaterial, FHitResult HitResult) {
|
||||
return;
|
||||
};
|
||||
|
||||
void AEBBullet::OnDeactivated_Implementation() {
|
||||
return;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2020 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBullet.h"
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "EBBarrel.h"
|
||||
#include "EBBullet.h"
|
||||
|
||||
void UEBBarrel::CalculateAimDirection(TSubclassOf<class AEBBullet> BulletClass, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime, float Step, int NumIterations) const {
|
||||
FVector StartLocation = GetComponentLocation();
|
||||
CalculateAimDirectionFromLocation(BulletClass, StartLocation, TargetLocation, TargetVelocity, AimDirection, PredictedTargetLocation, PredictedIntersectionLocation, PredictedFlightTime, Error, MaxTime, Step, NumIterations);
|
||||
}
|
||||
|
||||
void UEBBarrel::CalculateAimDirectionFromLocation(TSubclassOf<class AEBBullet> BulletClass, FVector StartLocation, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime, float Step, int NumIterations) const {
|
||||
if (!BulletClass->IsValidLowLevel()) {
|
||||
UE_LOG(LogTemp, Warning, TEXT("CalculateAimDirection - invalid bullet class"));
|
||||
return;
|
||||
}
|
||||
|
||||
AEBBullet* bullet = Cast<AEBBullet>(BulletClass->GetDefaultObject());
|
||||
|
||||
FVector AddVelocity = AdditionalVelocity;
|
||||
UPrimitiveComponent* parent = Cast<UPrimitiveComponent>(GetAttachParent());
|
||||
if (parent != nullptr) {
|
||||
if (parent->IsSimulatingPhysics()) {
|
||||
AddVelocity += parent->GetPhysicsLinearVelocityAtPoint(StartLocation) * InheritVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FVector InitialAimDirection = (TargetLocation - StartLocation).GetSafeNormal(); //initial prediction
|
||||
AimDirection = InitialAimDirection;
|
||||
FVector PreviousAimDirection = AimDirection;
|
||||
|
||||
for (int Iteration = 0; Iteration < NumIterations; Iteration++) {
|
||||
FVector CurrentBulletLocation = StartLocation;
|
||||
FVector Velocity = (AimDirection * (FMath::Lerp(MuzzleVelocityMultiplierMin, MuzzleVelocityMultiplierMax, 0.5) * FMath::Lerp(bullet->MuzzleVelocityMin, bullet->MuzzleVelocityMax, 0.5))) + AddVelocity;
|
||||
bool hit = 0;
|
||||
for (float time = 0; time <= MaxTime; time += Step) {
|
||||
FVector PreviousVelocity = Velocity;
|
||||
Velocity = bullet->UpdateVelocity(GetWorld(), CurrentBulletLocation, Velocity, Step);
|
||||
|
||||
FVector TraceVector = ((((PreviousVelocity + Velocity) * 0.5) - TargetVelocity) * Step);
|
||||
FVector TraceEndLocation = CurrentBulletLocation + TraceVector;
|
||||
FVector IntersectionPoint;
|
||||
|
||||
hit = FMath::SegmentPlaneIntersection(CurrentBulletLocation - TraceVector, TraceEndLocation, FPlane(TargetLocation, InitialAimDirection), IntersectionPoint); //actual hit test
|
||||
|
||||
if (hit) {
|
||||
PredictedIntersectionLocation = IntersectionPoint;
|
||||
FQuat AimCorrection = FQuat::FindBetween((IntersectionPoint - StartLocation), (TargetLocation - StartLocation));
|
||||
AimDirection = AimCorrection.RotateVector(AimDirection).GetSafeNormal();
|
||||
Error = (IntersectionPoint - TargetLocation).Size();
|
||||
|
||||
float AdditionalFlightTime = (FVector(CurrentBulletLocation - IntersectionPoint).Size() / TraceVector.Size()) * Step;
|
||||
PredictedFlightTime = time + AdditionalFlightTime;
|
||||
PredictedTargetLocation = TargetLocation + TargetVelocity * AdditionalFlightTime;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//no hit, keep going
|
||||
CurrentBulletLocation = TraceEndLocation;
|
||||
}
|
||||
if (!hit) {
|
||||
Error = 99999999999999999.0f;
|
||||
return; //no solution
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "EBBullet.h"
|
||||
|
||||
bool AEBBullet::CollisionFilter_Implementation(FHitResult HitResult) const{
|
||||
return true;
|
||||
};
|
||||
|
||||
FHitResult AEBBullet::FilterHits(TArray<FHitResult> Results, bool &hit) const{
|
||||
TArray<FHitResult> OutResults;
|
||||
|
||||
for (FHitResult Result : Results) {
|
||||
if (Result.bBlockingHit) {
|
||||
|
||||
hit = true;
|
||||
return Result;
|
||||
}else{
|
||||
if (CollisionFilter(Result)) {
|
||||
hit = true;
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hit = false;
|
||||
return FHitResult(); //blank
|
||||
};
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBallisticImpactComponent.h"
|
||||
#include "EBMathematicalBallistics.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
|
||||
UEBBallisticImpactComponent::UEBBallisticImpactComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
bEnableBallisticCalculations = true;
|
||||
bUseMathematicalPenetration = false;
|
||||
}
|
||||
|
||||
void UEBBallisticImpactComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
FVector UEBBallisticImpactComponent::CalculateBallisticImpact(
|
||||
const FVector& ImpactLocation,
|
||||
const FVector& ProjectileVelocity,
|
||||
const FMathematicalBulletProperties& BulletProperties,
|
||||
UPhysicalMaterial* HitMaterial,
|
||||
float& OutPenetrationDepth,
|
||||
bool& bOutDidPenetrate,
|
||||
FVector& OutExitLocation)
|
||||
{
|
||||
OutPenetrationDepth = 0.0f;
|
||||
bOutDidPenetrate = false;
|
||||
OutExitLocation = ImpactLocation;
|
||||
|
||||
if (!bEnableBallisticCalculations || !HitMaterial)
|
||||
{
|
||||
return ImpactLocation;
|
||||
}
|
||||
|
||||
// Get material response
|
||||
FEBMaterialResponseMapEntry MaterialResponse = GetMaterialResponse(HitMaterial);
|
||||
|
||||
if (MaterialResponse.NeverPenetrate)
|
||||
{
|
||||
return ImpactLocation;
|
||||
}
|
||||
|
||||
// Calculate penetration depth
|
||||
if (bUseMathematicalPenetration)
|
||||
{
|
||||
UEBMaterialPropertiesAsset* MaterialProps = GetMaterialProperties(HitMaterial);
|
||||
if (MaterialProps)
|
||||
{
|
||||
float VelocityMPS = ProjectileVelocity.Size() * 0.3048f; // feet to meters
|
||||
float ImpactAngle = CalculateImpactAngle(ProjectileVelocity, FVector::UpVector); // Simplified
|
||||
|
||||
OutPenetrationDepth = UEBMathematicalBallistics::CalculatePenetrationDepth(
|
||||
BulletProperties,
|
||||
MaterialProps->MaterialProperties,
|
||||
VelocityMPS,
|
||||
ImpactAngle
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use artistic approach
|
||||
float BaseDepth = ProjectileVelocity.Size() * 0.01f; // Simple velocity-based calculation
|
||||
OutPenetrationDepth = BaseDepth * MaterialResponse.PenetrationDepthMultiplier;
|
||||
}
|
||||
|
||||
// Check if penetration occurred
|
||||
float MinPenetrationThreshold = 1.0f; // 1cm minimum
|
||||
bOutDidPenetrate = OutPenetrationDepth > MinPenetrationThreshold;
|
||||
|
||||
if (bOutDidPenetrate)
|
||||
{
|
||||
// Calculate exit location
|
||||
FVector PenetrationDirection = ProjectileVelocity.GetSafeNormal();
|
||||
OutExitLocation = ImpactLocation + (PenetrationDirection * OutPenetrationDepth);
|
||||
}
|
||||
|
||||
// Fire event
|
||||
OnBallisticImpact.Broadcast(ImpactLocation, FVector::UpVector, HitMaterial, OutPenetrationDepth, bOutDidPenetrate);
|
||||
|
||||
return OutExitLocation;
|
||||
}
|
||||
|
||||
FVector UEBBallisticImpactComponent::CalculateRicochet(
|
||||
const FVector& ImpactLocation,
|
||||
const FVector& ImpactNormal,
|
||||
const FVector& ProjectileVelocity,
|
||||
const FMathematicalBulletProperties& BulletProperties,
|
||||
UPhysicalMaterial* HitMaterial,
|
||||
float& OutEnergyRetained,
|
||||
bool& bOutDidRicochet)
|
||||
{
|
||||
OutEnergyRetained = 0.0f;
|
||||
bOutDidRicochet = false;
|
||||
|
||||
if (!bEnableBallisticCalculations || !HitMaterial)
|
||||
{
|
||||
return ProjectileVelocity;
|
||||
}
|
||||
|
||||
// Get material response
|
||||
FEBMaterialResponseMapEntry MaterialResponse = GetMaterialResponse(HitMaterial);
|
||||
|
||||
if (MaterialResponse.NeverRicochet)
|
||||
{
|
||||
return ProjectileVelocity;
|
||||
}
|
||||
|
||||
// Calculate impact angle
|
||||
float ImpactAngle = CalculateImpactAngle(ProjectileVelocity, ImpactNormal);
|
||||
|
||||
// Simple ricochet probability based on angle
|
||||
float RicochetProbability = FMath::Clamp((90.0f - ImpactAngle) / 90.0f, 0.0f, 1.0f);
|
||||
RicochetProbability *= MaterialResponse.RicochetProbabilityMultiplier;
|
||||
|
||||
// Random chance for ricochet
|
||||
if (FMath::RandRange(0.0f, 1.0f) < RicochetProbability)
|
||||
{
|
||||
bOutDidRicochet = true;
|
||||
|
||||
// Calculate ricochet direction using reflection
|
||||
FVector RicochetDirection = UKismetMathLibrary::GetReflectionVector(ProjectileVelocity, ImpactNormal);
|
||||
|
||||
// Apply some randomness based on material response
|
||||
if (MaterialResponse.RicochetSpread > 0.0f)
|
||||
{
|
||||
FVector RandomOffset = FMath::VRand() * MaterialResponse.RicochetSpread;
|
||||
RicochetDirection = (RicochetDirection + RandomOffset).GetSafeNormal();
|
||||
}
|
||||
|
||||
// Calculate energy retention
|
||||
OutEnergyRetained = MaterialResponse.RicochetRestitution;
|
||||
|
||||
// Apply velocity reduction
|
||||
FVector NewVelocity = RicochetDirection * (ProjectileVelocity.Size() * OutEnergyRetained);
|
||||
|
||||
// Fire event
|
||||
OnBallisticRicochet.Broadcast(ImpactLocation, RicochetDirection, HitMaterial, OutEnergyRetained);
|
||||
|
||||
return NewVelocity;
|
||||
}
|
||||
|
||||
return ProjectileVelocity;
|
||||
}
|
||||
|
||||
UEBMaterialPropertiesAsset* UEBBallisticImpactComponent::GetMaterialProperties(UPhysicalMaterial* PhysicalMaterial)
|
||||
{
|
||||
if (!PhysicalMaterial)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// For now, use naming convention to find associated material properties
|
||||
// In production, you'd want a more robust system
|
||||
FString AssetName = PhysicalMaterial->GetName() + TEXT("_BallisticProps");
|
||||
FString PackageName = PhysicalMaterial->GetPackage()->GetName() + TEXT("_BallisticProps");
|
||||
|
||||
return LoadObject<UEBMaterialPropertiesAsset>(nullptr, *PackageName, nullptr, LOAD_NoWarn | LOAD_Quiet);
|
||||
}
|
||||
|
||||
FEBMaterialResponseMapEntry UEBBallisticImpactComponent::GetMaterialResponse(UPhysicalMaterial* PhysicalMaterial)
|
||||
{
|
||||
FEBMaterialResponseMapEntry DefaultResponse;
|
||||
|
||||
if (!MaterialResponseMap || !PhysicalMaterial)
|
||||
{
|
||||
return DefaultResponse;
|
||||
}
|
||||
|
||||
if (FEBMaterialResponseMapEntry* Found = MaterialResponseMap->Map.Find(PhysicalMaterial))
|
||||
{
|
||||
return *Found;
|
||||
}
|
||||
|
||||
return DefaultResponse;
|
||||
}
|
||||
|
||||
FVector UEBBallisticImpactComponent::CalculatePenetrationVector(
|
||||
const FVector& ImpactLocation,
|
||||
const FVector& ImpactNormal,
|
||||
const FVector& ProjectileVelocity,
|
||||
float PenetrationDepth)
|
||||
{
|
||||
FVector PenetrationDirection = ProjectileVelocity.GetSafeNormal();
|
||||
return ImpactLocation + (PenetrationDirection * PenetrationDepth);
|
||||
}
|
||||
|
||||
float UEBBallisticImpactComponent::CalculateImpactAngle(const FVector& ProjectileVelocity, const FVector& SurfaceNormal)
|
||||
{
|
||||
FVector NormalizedVelocity = ProjectileVelocity.GetSafeNormal();
|
||||
FVector NormalizedSurfaceNormal = SurfaceNormal.GetSafeNormal();
|
||||
|
||||
float DotProduct = FVector::DotProduct(-NormalizedVelocity, NormalizedSurfaceNormal);
|
||||
float AngleRadians = FMath::Acos(FMath::Clamp(DotProduct, -1.0f, 1.0f));
|
||||
|
||||
return FMath::RadiansToDegrees(AngleRadians);
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
// Copyright 2018 Mookie. All Rights Reserved.
|
||||
#include "EBBarrel.h"
|
||||
|
||||
UEBBarrel::UEBBarrel() {
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
bHiddenInGame = true;
|
||||
bAutoActivate = true;
|
||||
SetIsReplicatedByDefault(ReplicateVariables);
|
||||
|
||||
RandomStream.GenerateNewSeed();
|
||||
|
||||
GatlingRPS = FireRateMin;
|
||||
}
|
||||
|
||||
void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
if (ClientSideAim){
|
||||
if (GetOwner()->GetRemoteRole()==ROLE_Authority){
|
||||
TimeSinceAimUpdate += DeltaTime;
|
||||
|
||||
if (TimeSinceAimUpdate >= 1.0f / ClientAimUpdateFrequency) {
|
||||
Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
|
||||
Location = GetComponentTransform().GetLocation();
|
||||
ClientAim(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(),Location), Aim);
|
||||
TimeSinceAimUpdate = FMath::Fmod(TimeSinceAimUpdate, 1.0f / ClientAimUpdateFrequency);
|
||||
};
|
||||
}else{
|
||||
if (!RemoteAimReceived) {
|
||||
Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
|
||||
Location = GetComponentTransform().GetLocation();
|
||||
}
|
||||
else {
|
||||
FVector LocOffset = (Location - GetComponentLocation());
|
||||
if (LocOffset.Size() > ClientAimDistanceLimit) {
|
||||
//lag or cheater???
|
||||
Location = GetComponentLocation() + LocOffset.GetSafeNormal()*ClientAimDistanceLimit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
|
||||
Location = GetComponentTransform().GetLocation();
|
||||
}
|
||||
|
||||
//Only server can tick
|
||||
if (GetOwner()->GetLocalRole() == ROLE_Authority){
|
||||
|
||||
float RemainingDelta;
|
||||
|
||||
if (FireMode == EFireMode::FM_Gatling) {
|
||||
if (Spooling || (GatlingAutoSpool && Shooting)) {
|
||||
GatlingRPS = FMath::Lerp(GatlingRPS, FireRateMax, FMath::Min(GatlingSpoolUpTime*DeltaTime, 1.0f));
|
||||
}
|
||||
else {
|
||||
GatlingRPS = FMath::Lerp(GatlingRPS, FireRateMin, FMath::Min(GatlingSpoolUpTime*DeltaTime, 1.0f));
|
||||
}
|
||||
GatlingPhase += GatlingRPS*DeltaTime;
|
||||
for (int i = 1; i <= GatlingPhase; i++) {
|
||||
if (Cooldown <= 0.0f && LoadNext) {
|
||||
NextBullet();
|
||||
}
|
||||
|
||||
if (Shooting && ChamberedBullet != nullptr && (!ShootingBlocked)) {
|
||||
SpawnBullet(GetOwner(), Location, Aim);
|
||||
}
|
||||
}
|
||||
GatlingPhase = FMath::Fmod(GatlingPhase, 1.0f);
|
||||
|
||||
}
|
||||
else {
|
||||
RemainingDelta = DeltaTime;
|
||||
do {
|
||||
float step = FMath::Min(Cooldown, RemainingDelta);
|
||||
|
||||
Cooldown -= step;
|
||||
|
||||
RemainingDelta -= step;
|
||||
|
||||
if (Cooldown <= 0.0f && LoadNext) {
|
||||
NextBullet();
|
||||
}
|
||||
|
||||
//shoot when ready
|
||||
if (Shooting && ChamberedBullet != nullptr && (!ShootingBlocked)) {
|
||||
if (BurstRemaining > 0 || (FireMode != EFireMode::FM_Burst && FireMode != EFireMode::FM_InterBurst)) {
|
||||
SpawnBullet(GetOwner(), Location, Aim);
|
||||
}
|
||||
else {
|
||||
Shooting = false;
|
||||
}
|
||||
}
|
||||
} while (RemainingDelta > 0 && Cooldown > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UEBBarrel::NextBullet() {
|
||||
if (ChamberedBullet == nullptr) {
|
||||
if (Ammo.Num() > 0 && (CycleAmmoCount > 0 || CycleAmmoUnlimited || (!CycleAmmo))) {
|
||||
|
||||
//cycle ammo
|
||||
if (CycleAmmo) {
|
||||
if (CycleAmmoPos >= Ammo.Num()) { CycleAmmoPos = 0; }
|
||||
ChamberedBullet = Ammo[CycleAmmoPos];
|
||||
CycleAmmoPos++;
|
||||
|
||||
if (!CycleAmmoUnlimited) {
|
||||
CycleAmmoCount--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ChamberedBullet = Ammo[0];
|
||||
Ammo.RemoveAt(0, 1, EAllowShrinking::Yes);
|
||||
}
|
||||
|
||||
ReadyToShoot.Broadcast();
|
||||
}
|
||||
else {
|
||||
AmmoDepleted.Broadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UEBBarrel::SpawnBullet(AActor* Owner, FVector InLocation, FVector InAim) {
|
||||
TSubclassOf<class AEBBullet> BulletClass = ChamberedBullet;
|
||||
|
||||
if (BulletClass != nullptr) {
|
||||
FVector OutLocation;
|
||||
FVector OutAim;
|
||||
|
||||
InitialBulletTransform(InLocation, InAim, OutLocation, OutAim);
|
||||
|
||||
AEBBullet* Default = Cast<AEBBullet>(BulletClass->GetDefaultObject());
|
||||
|
||||
float BulletSpread = Default->Spread;
|
||||
if (Default->SpreadBias > 0.0f) {
|
||||
float SpreadMult = FMath::Pow(FMath::FRand(), Default->SpreadBias);
|
||||
BulletSpread *= SpreadMult;
|
||||
}
|
||||
float BarrelSpread = Spread;
|
||||
if (SpreadBias > 0.0f) {
|
||||
float SpreadMult = FMath::Pow(FMath::FRand(), SpreadBias);
|
||||
BarrelSpread *= SpreadMult;
|
||||
}
|
||||
|
||||
float TotalSpread = BulletSpread+BarrelSpread;
|
||||
|
||||
OutAim = RandomStream.VRandCone(OutAim,TotalSpread);
|
||||
float BulletVelocity = FMath::Lerp(MuzzleVelocityMultiplierMin* Default->MuzzleVelocityMin, MuzzleVelocityMultiplierMax*Default->MuzzleVelocityMax, RandomStream.FRand());
|
||||
FVector Velocity = OutAim*BulletVelocity;
|
||||
|
||||
//get parent physics body
|
||||
UPrimitiveComponent* parent = Cast<UPrimitiveComponent>(GetAttachParent());
|
||||
Velocity += AdditionalVelocity;
|
||||
|
||||
if (parent != nullptr) {
|
||||
|
||||
if (parent->IsSimulatingPhysics()) {
|
||||
Velocity += parent->GetPhysicsLinearVelocityAtPoint(OutLocation)*InheritVelocity;
|
||||
}
|
||||
|
||||
if (Default->Shotgun) {
|
||||
ApplyRecoil(parent, OutLocation, -Velocity*Default->Mass*RecoilMultiplier*Default->ShotCount);
|
||||
}
|
||||
else{
|
||||
ApplyRecoil(parent, OutLocation, -Velocity*Default->Mass*RecoilMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
BeforeShotFired.Broadcast();
|
||||
|
||||
AEBBullet::SpawnWithExactVelocity(BulletClass, Owner, Owner->GetInstigator(), OutLocation, Velocity);
|
||||
|
||||
//spend ammo
|
||||
ChamberedBullet = nullptr;
|
||||
if (FireMode != EFireMode::FM_Gatling) {
|
||||
Cooldown = 1.0f / FMath::Lerp(FireRateMin, FireRateMax, RandomStream.FRand());
|
||||
}
|
||||
|
||||
//fire modes
|
||||
switch (FireMode) {
|
||||
case EFireMode::FM_Auto:
|
||||
LoadNext = true;
|
||||
break;
|
||||
|
||||
case EFireMode::FM_Burst:
|
||||
LoadNext = true;
|
||||
break;
|
||||
|
||||
case EFireMode::FM_InterBurst:
|
||||
LoadNext = true;
|
||||
break;
|
||||
|
||||
case EFireMode::FM_Semiauto:
|
||||
Shooting = false;
|
||||
LoadNext = true;
|
||||
break;
|
||||
|
||||
case EFireMode::FM_Manual:
|
||||
Shooting = false;
|
||||
LoadNext = false;
|
||||
break;
|
||||
|
||||
case EFireMode::FM_Slamfire:
|
||||
LoadNext = false;
|
||||
break;
|
||||
|
||||
case EFireMode::FM_Gatling:
|
||||
LoadNext = true;
|
||||
break;
|
||||
};
|
||||
|
||||
if (BurstRemaining > 0) {
|
||||
BurstRemaining--;
|
||||
}
|
||||
else {
|
||||
if (FireMode == EFireMode::FM_Burst || FireMode == EFireMode::FM_InterBurst) {
|
||||
Cooldown = FMath::Max(Cooldown, BurstCooldown);
|
||||
}
|
||||
}
|
||||
|
||||
if (ReplicateShotFiredEvents) {
|
||||
ShotFiredMulticast();
|
||||
}
|
||||
else {
|
||||
ShotFired.Broadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UEBBarrel::InitialBulletTransform_Implementation(FVector InLocation, FVector InDirection, FVector& OutLocation, FVector& OutDirection) {
|
||||
OutLocation = InLocation;
|
||||
OutDirection = InDirection;
|
||||
}
|
||||
|
||||
void UEBBarrel::ApplyRecoil_Implementation(UPrimitiveComponent* Component, FVector InLocation, FVector Impulse){
|
||||
if (Component->IsSimulatingPhysics()) {
|
||||
Component->AddImpulseAtLocation(Impulse, InLocation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
// Copyright 2018 Mookie. All Rights Reserved.
|
||||
#include "EBBullet.h"
|
||||
|
||||
// Sets default values
|
||||
AEBBullet::AEBBullet() {
|
||||
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
SetTickGroup(ETickingGroup::TG_PrePhysics);
|
||||
|
||||
// Create the new ballistic impact component
|
||||
BallisticImpactComponent = CreateDefaultSubobject<UEBBallisticImpactComponent>(TEXT("BallisticImpactComponent"));
|
||||
BallisticImpactComponent->MaterialResponseMap = MaterialResponseMap;
|
||||
}
|
||||
|
||||
// Called when the game starts or when spawned
|
||||
void AEBBullet::BeginPlay() {
|
||||
SetActorEnableCollision(AllowComponentCollisions);
|
||||
|
||||
// Update ballistic impact component with current settings
|
||||
if (BallisticImpactComponent) {
|
||||
BallisticImpactComponent->MaterialResponseMap = MaterialResponseMap;
|
||||
BallisticImpactComponent->bUseMathematicalPenetration = UseMathematicalPhysics;
|
||||
BallisticImpactComponent->bEnableBallisticCalculations = UseNewImpactSystem;
|
||||
}
|
||||
|
||||
if(!IsRecycled){
|
||||
Super::BeginPlay();
|
||||
IsRecycled = true;
|
||||
}
|
||||
else{
|
||||
ReceiveBeginPlay();
|
||||
}
|
||||
|
||||
if (SafeLaunch) {
|
||||
OwnerSafe = true;
|
||||
}
|
||||
|
||||
if (DoFirstStepImmediately) {
|
||||
float DeltaTime = GetWorld()->GetDeltaSeconds();
|
||||
|
||||
if (RandomFirstStepDelta) {
|
||||
DeltaTime *= RandomStream.FRand();
|
||||
};
|
||||
|
||||
if (FixedStep) {
|
||||
Step(FixedStepSeconds);
|
||||
}
|
||||
else {
|
||||
Step(DeltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void AEBBullet::Tick(float DeltaTime) {
|
||||
Super::Tick(DeltaTime);
|
||||
|
||||
if (FixedStep) {
|
||||
AccumulatedDelta += DeltaTime;
|
||||
|
||||
while (AccumulatedDelta >= FixedStepSeconds) {
|
||||
Step(FixedStepSeconds);
|
||||
AccumulatedDelta -= FixedStepSeconds;
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
Step(DeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::Step(float DeltaTime) {
|
||||
FVector start = GetActorLocation();
|
||||
bool sendUpdate = false;
|
||||
|
||||
if (Retrace && CanRetrace) {
|
||||
//time travel
|
||||
float remainingTime = LastTraceDelta;
|
||||
int remainingSteps = MaxTracesPerStep;
|
||||
FVector PreviousVelocity = LastTracePrevVelocity;
|
||||
SetActorLocation(LastTraceStart);
|
||||
Velocity = LastTraceVelocity;
|
||||
|
||||
do {
|
||||
if (RetraceOnAnotherChannel) {
|
||||
remainingTime = Trace(GetActorLocation(),
|
||||
PreviousVelocity,
|
||||
remainingTime,
|
||||
RetraceChannel);
|
||||
}
|
||||
else {
|
||||
remainingTime = Trace(GetActorLocation(),
|
||||
PreviousVelocity,
|
||||
remainingTime,
|
||||
TraceChannel);
|
||||
}
|
||||
PreviousVelocity = Velocity;
|
||||
remainingSteps -= 1;
|
||||
if (remainingTime > 0.0f) { sendUpdate = true; };
|
||||
} while (remainingTime > 0.0f && remainingSteps > 0);
|
||||
}
|
||||
CanRetrace = false;
|
||||
|
||||
FVector PreviousVelocity = Velocity;
|
||||
Velocity = UpdateVelocity(GetWorld(), GetActorLocation(), Velocity, DeltaTime);
|
||||
|
||||
//trace
|
||||
float remainingTime = DeltaTime;
|
||||
int remainingSteps = MaxTracesPerStep;
|
||||
do {
|
||||
remainingTime = Trace(GetActorLocation(),
|
||||
PreviousVelocity,
|
||||
remainingTime,
|
||||
TraceChannel
|
||||
);
|
||||
PreviousVelocity = Velocity;
|
||||
remainingSteps -= 1;
|
||||
if (remainingTime > 0.0f) { sendUpdate = true; };
|
||||
} while (remainingTime > 0.0f && remainingSteps > 0);
|
||||
|
||||
if (sendUpdate) {
|
||||
if (ReliableReplication) {
|
||||
VelocityChangeBroadcastReliable(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(),GetActorLocation()), Velocity);
|
||||
}
|
||||
else {
|
||||
VelocityChangeBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(), GetActorLocation()), Velocity);
|
||||
}
|
||||
}
|
||||
|
||||
if(SafeDelay <= 0.0f){
|
||||
OwnerSafe = false;
|
||||
}
|
||||
else {
|
||||
SafeDelay -= DeltaTime;
|
||||
}
|
||||
|
||||
if (RotateActor) {
|
||||
FRotator NewRot = UKismetMathLibrary::MakeRotFromX(Velocity);
|
||||
NewRot.Roll = GetActorRotation().Roll;
|
||||
SetActorRotation(NewRot);
|
||||
}
|
||||
}
|
||||
|
||||
float AEBBullet::GetCurveValue(const UCurveFloat* curve, float in, float deflt) const {
|
||||
if (curve == nullptr) return deflt;
|
||||
return curve->GetFloatValue(in);
|
||||
}
|
||||
|
||||
void AEBBullet::ApplyWorldOffset(const FVector& InOffset, bool bWorldShift) {
|
||||
Super::ApplyWorldOffset(InOffset, bWorldShift);
|
||||
LastTraceStart += InOffset;
|
||||
}
|
||||
|
||||
// Mathematical Physics Function Implementations
|
||||
float AEBBullet::GetEffectiveMass() const
|
||||
{
|
||||
if (UseMathematicalPhysics && BulletPropertiesAsset)
|
||||
{
|
||||
return BulletPropertiesAsset->BulletProperties.GetMassKg();
|
||||
}
|
||||
return Mass;
|
||||
}
|
||||
|
||||
float AEBBullet::GetEffectiveDiameter() const
|
||||
{
|
||||
if (UseMathematicalPhysics && BulletPropertiesAsset)
|
||||
{
|
||||
return BulletPropertiesAsset->BulletProperties.GetDiameterCm();
|
||||
}
|
||||
return Diameter;
|
||||
}
|
||||
|
||||
float AEBBullet::GetEffectiveDragCoefficient(float MachNumber) const
|
||||
{
|
||||
if (UseMathematicalPhysics && BulletPropertiesAsset)
|
||||
{
|
||||
return UEBMathematicalBallistics::CalculateDragCoefficient(
|
||||
BulletPropertiesAsset->BulletProperties, MachNumber);
|
||||
}
|
||||
|
||||
// Use artistic drag calculation
|
||||
float DragCoeff = GetCurveValue(MachDragCurve, MachNumber, 0.5f);
|
||||
return DragCoeff * FormFactor;
|
||||
}
|
||||
|
||||
float AEBBullet::CalculateMathematicalPenetration(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const
|
||||
{
|
||||
if (!UseMathematicalPhysics || !BulletPropertiesAsset)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
FMathematicalMaterialProperties MaterialProps = GetMaterialProperties(Material);
|
||||
|
||||
return UEBMathematicalBallistics::CalculatePenetrationDepth(
|
||||
BulletPropertiesAsset->BulletProperties,
|
||||
MaterialProps,
|
||||
VelocityMPS,
|
||||
ImpactAngle
|
||||
);
|
||||
}
|
||||
|
||||
float AEBBullet::CalculateMathematicalRicochetProbability(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const
|
||||
{
|
||||
if (!UseMathematicalPhysics || !BulletPropertiesAsset)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
FMathematicalMaterialProperties MaterialProps = GetMaterialProperties(Material);
|
||||
|
||||
return UEBMathematicalBallistics::CalculateRicochetProbability(
|
||||
BulletPropertiesAsset->BulletProperties,
|
||||
MaterialProps,
|
||||
VelocityMPS,
|
||||
ImpactAngle
|
||||
);
|
||||
}
|
||||
|
||||
FMathematicalMaterialProperties AEBBullet::GetMaterialProperties(UPhysicalMaterial* Material) const
|
||||
{
|
||||
// Default properties for unknown materials
|
||||
FMathematicalMaterialProperties DefaultProps;
|
||||
|
||||
if (!Material || !MaterialResponseMap)
|
||||
{
|
||||
return DefaultProps;
|
||||
}
|
||||
|
||||
// Check if we have custom properties for this material
|
||||
if (MaterialResponseMap->Map.Contains(Material))
|
||||
{
|
||||
const FEBMaterialResponseMapEntry& Entry = MaterialResponseMap->Map[Material];
|
||||
if (Entry.UseMathematicalProperties)
|
||||
{
|
||||
return Entry.MathematicalProperties;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a material properties asset, use that as fallback
|
||||
if (MaterialPropertiesAsset)
|
||||
{
|
||||
return MaterialPropertiesAsset->MaterialProperties;
|
||||
}
|
||||
|
||||
return DefaultProps;
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBMathematicalBallistics.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Math/UnrealMathUtility.h"
|
||||
|
||||
float UEBMathematicalBallistics::CalculatePenetrationDepth(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS,
|
||||
float ImpactAngleDegrees)
|
||||
{
|
||||
// Calculate kinetic energy in joules
|
||||
float KineticEnergy = CalculateKineticEnergy(BulletProps, VelocityMPS);
|
||||
|
||||
// Calculate sectional density
|
||||
float SectionalDensity = BulletProps.GetSectionalDensity();
|
||||
|
||||
// Calculate hardness ratio
|
||||
float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness);
|
||||
|
||||
// Calculate angle factor (normal impact = 1.0, grazing = 0.0)
|
||||
float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees);
|
||||
|
||||
// Calculate shape factor based on bullet type
|
||||
float ShapeFactor = CalculateShapeFactorFromBulletType(BulletProps.BulletType);
|
||||
|
||||
// Modified Taylor-Hopkinson equation for penetration depth
|
||||
// P = (K * E * SD * HF * AF * SF) / (ρ * σ)
|
||||
// Where: P = penetration depth, K = constant, E = kinetic energy, SD = sectional density
|
||||
// HF = hardness factor, AF = angle factor, SF = shape factor, ρ = density, σ = yield strength
|
||||
|
||||
float Constant = 0.0012f; // Empirical constant
|
||||
float Penetration = (Constant * KineticEnergy * SectionalDensity * HardnessRatio * AngleFactor * ShapeFactor) /
|
||||
(MaterialProps.DensityGPerCm3 * MaterialProps.YieldStrengthMPa);
|
||||
|
||||
return FMath::Max(0.0f, Penetration);
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateResidualVelocity(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS,
|
||||
float ThicknessCM,
|
||||
float ImpactAngleDegrees)
|
||||
{
|
||||
// Calculate ballistic limit velocity
|
||||
float BallisticLimit = CalculateRechtIpsonVelocity(BulletProps, MaterialProps, ThicknessCM);
|
||||
|
||||
// Adjust for impact angle
|
||||
float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees);
|
||||
BallisticLimit = BallisticLimit / AngleFactor;
|
||||
|
||||
// If impact velocity is below ballistic limit, no penetration
|
||||
if (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);
|
||||
|
||||
// Apply energy absorption factor
|
||||
ResidualVelocity *= (1.0f - MaterialProps.EnergyAbsorptionCoefficient);
|
||||
|
||||
return FMath::Max(0.0f, ResidualVelocity);
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateRicochetProbability(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS,
|
||||
float ImpactAngleDegrees)
|
||||
{
|
||||
// Calculate critical ricochet angle
|
||||
float CriticalAngle = CalculateCriticalRicochetAngle(BulletProps, MaterialProps, VelocityMPS);
|
||||
|
||||
// If impact angle is greater than critical angle, no ricochet
|
||||
if (ImpactAngleDegrees > CriticalAngle)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Calculate ricochet probability based on angle and material properties
|
||||
float AngleRatio = ImpactAngleDegrees / CriticalAngle;
|
||||
float BaseRicochetProbability = 1.0f - FMath::Pow(AngleRatio, 2.0f);
|
||||
|
||||
// Adjust for hardness ratio
|
||||
float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness);
|
||||
float HardnessAdjustment = FMath::Clamp(HardnessRatio, 0.1f, 2.0f);
|
||||
|
||||
// Adjust for velocity (higher velocity reduces ricochet probability)
|
||||
float VelocityFactor = CalculateVelocityFactor(VelocityMPS, 500.0f);
|
||||
|
||||
float RicochetProbability = BaseRicochetProbability * HardnessAdjustment * VelocityFactor;
|
||||
|
||||
return FMath::Clamp(RicochetProbability, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateBulletExpansion(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS)
|
||||
{
|
||||
// Convert velocity to FPS for comparison with expansion threshold
|
||||
float VelocityFPS = ConvertMPStoFPS(VelocityMPS);
|
||||
|
||||
// No expansion if velocity is below threshold
|
||||
if (VelocityFPS < BulletProps.ExpansionVelocityThreshold)
|
||||
{
|
||||
return 1.0f; // No expansion
|
||||
}
|
||||
|
||||
// Calculate expansion based on velocity and bullet type
|
||||
float VelocityRatio = VelocityFPS / BulletProps.ExpansionVelocityThreshold;
|
||||
float ExpansionFactor = 1.0f;
|
||||
|
||||
switch (BulletProps.BulletType)
|
||||
{
|
||||
case EBulletType::BT_HollowPoint:
|
||||
ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f));
|
||||
break;
|
||||
case EBulletType::BT_SoftPoint:
|
||||
ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier * 0.8f, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f));
|
||||
break;
|
||||
case EBulletType::BT_Frangible:
|
||||
ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier * 1.2f, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f));
|
||||
break;
|
||||
case EBulletType::BT_FullMetalJacket:
|
||||
case EBulletType::BT_ArmorPiercing:
|
||||
ExpansionFactor = 1.0f; // No expansion
|
||||
break;
|
||||
default:
|
||||
ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier * 0.6f, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f));
|
||||
break;
|
||||
}
|
||||
|
||||
return ExpansionFactor;
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateKineticEnergy(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
float VelocityMPS)
|
||||
{
|
||||
// KE = 0.5 * m * v^2
|
||||
float MassKg = BulletProps.GetMassKg();
|
||||
return 0.5f * MassKg * VelocityMPS * VelocityMPS;
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateMomentum(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
float VelocityMPS)
|
||||
{
|
||||
// p = m * v
|
||||
float MassKg = BulletProps.GetMassKg();
|
||||
return MassKg * VelocityMPS;
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateDragCoefficient(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
float MachNumber)
|
||||
{
|
||||
// Calculate drag coefficient from ballistic coefficient
|
||||
// Standard drag coefficient for G1 projectile at given Mach number
|
||||
float StandardDrag = 0.5f; // Simplified - normally would use complex curve
|
||||
|
||||
// Adjust for actual ballistic coefficient
|
||||
float BC = BulletProps.UseG7Model ? BulletProps.BallisticCoefficientG7 : BulletProps.BallisticCoefficientG1;
|
||||
|
||||
// CD = Standard_CD / BC
|
||||
return StandardDrag / BC;
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateTaylorHopkinsonLimit(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps)
|
||||
{
|
||||
// Taylor-Hopkinson limit: t = K * ρ * σ / (SD * V^2)
|
||||
// Rearranged to solve for limiting thickness
|
||||
float SectionalDensity = BulletProps.GetSectionalDensity();
|
||||
float Constant = 0.001f; // Empirical constant
|
||||
|
||||
return Constant * MaterialProps.DensityGPerCm3 * MaterialProps.YieldStrengthMPa / SectionalDensity;
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateRechtIpsonVelocity(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float ThicknessCM)
|
||||
{
|
||||
// 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
|
||||
|
||||
float Constant = 2.0f; // Empirical constant
|
||||
float CrossSectionArea = BulletProps.GetCrossSectionCm2();
|
||||
float BulletDensity = BulletProps.GetMassKg() / (CrossSectionArea * BulletProps.LengthInches * 2.54f); // Rough approximation
|
||||
|
||||
float BallisticLimit = FMath::Sqrt(Constant * MaterialProps.YieldStrengthMPa * ThicknessCM /
|
||||
(BulletDensity * CrossSectionArea));
|
||||
|
||||
return BallisticLimit;
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateCriticalRicochetAngle(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS)
|
||||
{
|
||||
// Calculate critical ricochet angle using empirical formula
|
||||
// Critical angle decreases with harder bullets and increases with harder targets
|
||||
|
||||
float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness);
|
||||
float VelocityFactor = CalculateVelocityFactor(VelocityMPS, 300.0f);
|
||||
|
||||
// Base critical angle (degrees)
|
||||
float BaseCriticalAngle = 20.0f;
|
||||
|
||||
// Adjust for hardness and velocity
|
||||
float CriticalAngle = BaseCriticalAngle * HardnessRatio * VelocityFactor;
|
||||
|
||||
return FMath::Clamp(CriticalAngle, 5.0f, 45.0f);
|
||||
}
|
||||
|
||||
// Helper function implementations
|
||||
float UEBMathematicalBallistics::CalculateHardnessRatio(float BulletHardness, float MaterialHardness)
|
||||
{
|
||||
// Hardness ratio affects penetration and ricochet
|
||||
return FMath::Max(0.1f, BulletHardness / MaterialHardness);
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateVelocityFactor(float Velocity, float ThresholdVelocity)
|
||||
{
|
||||
// Velocity factor for various calculations
|
||||
return FMath::Clamp(Velocity / ThresholdVelocity, 0.1f, 5.0f);
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateAngleFactor(float ImpactAngleDegrees)
|
||||
{
|
||||
// Convert to radians and calculate cosine (normal impact = 1.0, grazing = 0.0)
|
||||
float AngleRadians = FMath::DegreesToRadians(ImpactAngleDegrees);
|
||||
return FMath::Cos(AngleRadians);
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateShapeFactorFromBulletType(EBulletType BulletType)
|
||||
{
|
||||
// Shape factor affects penetration efficiency
|
||||
switch (BulletType)
|
||||
{
|
||||
case EBulletType::BT_ArmorPiercing:
|
||||
return 1.3f;
|
||||
case EBulletType::BT_FullMetalJacket:
|
||||
return 1.0f;
|
||||
case EBulletType::BT_SoftPoint:
|
||||
return 0.9f;
|
||||
case EBulletType::BT_HollowPoint:
|
||||
return 0.8f;
|
||||
case EBulletType::BT_Frangible:
|
||||
return 0.6f;
|
||||
case EBulletType::BT_Wadcutter:
|
||||
return 0.7f;
|
||||
case EBulletType::BT_Match:
|
||||
return 1.1f;
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateMaterialFactor(EBulletMaterial BulletMaterial)
|
||||
{
|
||||
// Material factor affects penetration and expansion
|
||||
switch (BulletMaterial)
|
||||
{
|
||||
case EBulletMaterial::BM_Steel:
|
||||
return 1.4f;
|
||||
case EBulletMaterial::BM_Tungsten:
|
||||
return 1.8f;
|
||||
case EBulletMaterial::BM_Brass:
|
||||
return 1.2f;
|
||||
case EBulletMaterial::BM_Copper:
|
||||
return 1.1f;
|
||||
case EBulletMaterial::BM_CopperJacket:
|
||||
return 1.0f;
|
||||
case EBulletMaterial::BM_Lead:
|
||||
return 0.8f;
|
||||
case EBulletMaterial::BM_LeadAntimony:
|
||||
return 0.9f;
|
||||
case EBulletMaterial::BM_Bismuth:
|
||||
return 0.7f;
|
||||
case EBulletMaterial::BM_Zinc:
|
||||
return 0.9f;
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EasyBallistics.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FEasyBallisticsModule"
|
||||
|
||||
void FEasyBallisticsModule::StartupModule()
|
||||
{
|
||||
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||
}
|
||||
|
||||
void FEasyBallisticsModule::ShutdownModule()
|
||||
{
|
||||
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||
// we call this function before unloading the module.
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FEasyBallisticsModule, EasyBallistics)
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBullet.h"
|
||||
|
||||
FVector AEBBullet::GetWind_Implementation(UWorld* World, FVector Location) const{
|
||||
return Wind;
|
||||
}
|
||||
|
||||
float AEBBullet::GetAirDensity_Implementation(UWorld* World, FVector Location) const{
|
||||
switch (AtmosphereType) {
|
||||
case (EEBAtmosphereType::AT_Curve): {
|
||||
float airmp = SeaLevelAirDensity / GetCurveValue(AirDensityCurve, 0, SeaLevelAirDensity);
|
||||
return GetCurveValue(AirDensityCurve, GetAltitude(World, Location) / WorldScale, SeaLevelAirDensity)* airmp;
|
||||
}
|
||||
case (EEBAtmosphereType::AT_Earth): {
|
||||
return GetAltitudeDensity(GetAltitude(World, Location) / WorldScale / 100.0f);
|
||||
}
|
||||
default:{
|
||||
return SeaLevelAirDensity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float AEBBullet::GetSpeedOfSound_Implementation(UWorld* World, FVector Location) const{
|
||||
if (!SpeedOfSoundVariesWithAltitude) {
|
||||
return SeaLevelSpeedOfSound * WorldScale;
|
||||
}
|
||||
|
||||
float Altitude = GetAltitude(World, Location);
|
||||
float soundvmp = SeaLevelSpeedOfSound / GetCurveValue(SpeedOfSoundCurve, 0, SeaLevelSpeedOfSound);
|
||||
return GetCurveValue(SpeedOfSoundCurve, Altitude, SeaLevelSpeedOfSound)*WorldScale*soundvmp;
|
||||
}
|
||||
|
||||
|
||||
float AEBBullet::GetAltitudePressure(float AltitudeMeter) const {
|
||||
return FMath::Max(SeaLevelAirPressure * FMath::Pow((1 - (0.0000225577 * AltitudeMeter)), 5.25588), 0.0f);
|
||||
}
|
||||
|
||||
float AEBBullet::GetAltitudeTemperature(float AltitudeMeter) const {
|
||||
return SeaLevelAirTemperature - (TemperatureLapseRate * FMath::Min(AltitudeMeter, TropopauseAltitude));
|
||||
}
|
||||
|
||||
float AEBBullet::GetAltitudeDensity(float AltitudeMeter) const {
|
||||
float Temperature = GetAltitudeTemperature(AltitudeMeter);
|
||||
float Pressure = GetAltitudePressure(AltitudeMeter);
|
||||
return Pressure * 100.0f / ((Temperature + 273.15) * SpecificGasConstant);
|
||||
}
|
||||
|
||||
float AEBBullet::GetAltitude(UWorld* World, FVector Location) const{
|
||||
FVector DistanceFromOrigin = (Location - WorldCenterLocation + FVector(World->OriginLocation));
|
||||
if (SphericalAltitude)
|
||||
{
|
||||
return (DistanceFromOrigin.Size() - SeaLevelRadius);
|
||||
}
|
||||
else {
|
||||
return DistanceFromOrigin.Z;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2020 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBullet.h"
|
||||
|
||||
float AEBBullet::PenetrationTrace(FVector StartLocation, FVector EndLocation, TWeakObjectPtr<UPrimitiveComponent, FWeakObjectPtr> Component, EPenTraceType PenTraceType, TEnumAsByte<ECollisionChannel> CollisionChannel, FVector &ExitLocation, FVector &ExitNormal) {
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.bTraceComplex = TraceComplex;
|
||||
QueryParams.bFindInitialOverlaps = true;
|
||||
|
||||
FHitResult Result;
|
||||
|
||||
switch (PenTraceType) {
|
||||
case(EPenTraceType::PT_BackTrace): {
|
||||
bool Hit = GetWorld()->LineTraceSingleByChannel(Result, EndLocation, StartLocation, CollisionChannel, QueryParams);
|
||||
if (!Hit) return 1.0f;
|
||||
ExitNormal = Result.Normal;
|
||||
ExitLocation = Result.Location;
|
||||
return (1.0f - Result.Time);
|
||||
}
|
||||
|
||||
case(EPenTraceType::PT_ByComponent): {
|
||||
bool Hit = Component->LineTraceComponent(Result, EndLocation, StartLocation, QueryParams);
|
||||
if (!Hit) return 1.0f;
|
||||
ExitNormal = Result.Normal;
|
||||
ExitLocation = Result.Location;
|
||||
return (1.0f - Result.Time);
|
||||
}
|
||||
|
||||
case(EPenTraceType::PT_TwoSidedGeometry): {
|
||||
bool Hit = GetWorld()->LineTraceSingleByChannel(Result, StartLocation, EndLocation, CollisionChannel, QueryParams);
|
||||
if (!Hit) return 1.0f;
|
||||
ExitLocation = Result.Location;
|
||||
ExitNormal = -Result.Normal;
|
||||
return Result.Time;
|
||||
}
|
||||
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
#include "EBBullet.h"
|
||||
|
||||
void AEBBullet::Deactivate() {
|
||||
//server only
|
||||
if (!HasAuthority()) { return; }
|
||||
OnDeactivated();
|
||||
this->DeactivateToPool();
|
||||
DeactivationBroadcast();
|
||||
}
|
||||
|
||||
AEBBullet* AEBBullet::GetFromPool(UWorld* World, UClass* BulletClass) {
|
||||
AEBBullet* Pool = Cast<AEBBullet>(BulletClass->GetDefaultObject());
|
||||
|
||||
if (Pool) {
|
||||
//find first of correct class;
|
||||
bool CleanupRequired=false;
|
||||
|
||||
int32 FoundIndex = Pool->Pooled.IndexOfByPredicate(
|
||||
[&](auto InItem) {
|
||||
if (InItem.IsValid() && InItem->GetWorld() == World) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
CleanupRequired = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
TWeakObjectPtr<AEBBullet> Found = nullptr;
|
||||
if (FoundIndex != INDEX_NONE) {
|
||||
Found = Pool->Pooled[FoundIndex];
|
||||
Pool->Pooled.RemoveAtSwap(FoundIndex,EAllowShrinking::Yes);
|
||||
}
|
||||
|
||||
if (CleanupRequired) {
|
||||
#ifdef WITH_EDITOR
|
||||
if (Pool->DebugPooling) {
|
||||
GEngine->AddOnScreenDebugMessage(2, 2, FColor::White, TEXT("Invalid reference in pool, cleaning up"));
|
||||
}
|
||||
#endif
|
||||
Pool->Pooled.RemoveAll([&](auto InItem) {
|
||||
if (InItem.IsValid() && InItem->GetWorld() == World) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return(Found.Get());
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AEBBullet* AEBBullet::SpawnOrReactivate(UWorld* World, TSubclassOf<class AEBBullet> BulletClass, const FTransform& Transform, FVector BulletVelocity, AActor* BulletOwner, APawn* BulletInstigator) {
|
||||
AEBBullet* bullet;
|
||||
|
||||
AEBBullet* Recycled = GetFromPool(World, BulletClass);
|
||||
|
||||
if (Recycled) {
|
||||
AEBBullet* Default = Cast<AEBBullet>(BulletClass->GetDefaultObject());
|
||||
|
||||
Recycled->Reset();
|
||||
|
||||
Recycled->SetOwner(BulletOwner);
|
||||
Recycled->SetInstigator(BulletInstigator);
|
||||
Recycled->SetActorTransform(Transform,false,nullptr,ETeleportType::TeleportPhysics);
|
||||
Recycled->Velocity = BulletVelocity;
|
||||
Recycled->SetActorHiddenInGame(Default->IsHidden());
|
||||
Recycled->SetActorTickEnabled(true);
|
||||
Recycled->CanRetrace = false;
|
||||
Recycled->IgnoredActors = Default->IgnoredActors;
|
||||
Recycled->SafeDelay = Default->SafeDelay;
|
||||
Recycled->SetLifeSpan(Default->InitialLifeSpan);
|
||||
Recycled->FinishSpawning(Transform);
|
||||
//if (!Recycled->HasActorBegunPlay()){ Recycled->BeginPlay(); }
|
||||
//Recycled->ReactivationBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(Recycled->GetWorld(), Transform.GetLocation()), BulletVelocity, BulletOwner, BulletInstigator);
|
||||
#ifdef WITH_EDITOR
|
||||
if (Recycled->DebugPooling) {
|
||||
GEngine->AddOnScreenDebugMessage(0, 2, FColor::Green, TEXT("Recycling pooled bullet"));
|
||||
}
|
||||
#endif
|
||||
return Recycled;
|
||||
}
|
||||
else {
|
||||
bullet = Cast<AEBBullet>(World->SpawnActorDeferred<AEBBullet>(BulletClass, Transform, BulletOwner, BulletInstigator));
|
||||
bullet->RandomStream.GenerateNewSeed();
|
||||
bullet->Velocity = BulletVelocity;
|
||||
bullet->FinishSpawning(Transform);
|
||||
//UGameplayStatics::FinishSpawningActor(bullet, Transform);
|
||||
#ifdef WITH_EDITOR
|
||||
if (bullet->DebugPooling) {
|
||||
GEngine->AddOnScreenDebugMessage(0, 2, FColor::Orange, TEXT("Spawning new bullet"));
|
||||
}
|
||||
#endif
|
||||
return bullet;
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::FinishSpawning(FTransform Transform) {
|
||||
if(IsRecycled){
|
||||
if (!HasActorBegunPlay()){
|
||||
BeginPlay();
|
||||
}
|
||||
ReactivationBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(this->GetWorld(), Transform.GetLocation()), this->Velocity, this->GetOwner(), this->GetInstigator());
|
||||
}else{
|
||||
UGameplayStatics::FinishSpawningActor(this, Transform);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::ReactivationBroadcast_Implementation(FVector_NetQuantize NewLocation, FVector NewVelocity, AActor* BulletOwner, APawn* BulletInstigator) {
|
||||
if (!HasAuthority()) {
|
||||
AEBBullet* Default = Cast<AEBBullet>(this->StaticClass()->GetDefaultObject());
|
||||
|
||||
SetOwner(BulletOwner);
|
||||
SetInstigator(BulletInstigator);
|
||||
|
||||
SetActorLocation(UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(), NewLocation));
|
||||
Velocity = NewVelocity;
|
||||
CanRetrace = false;
|
||||
|
||||
SetActorHiddenInGame(Default->IsHidden());
|
||||
SetActorTickEnabled(true);
|
||||
SafeDelay = Default->SafeDelay;
|
||||
OwnerSafe = Default->SafeLaunch;
|
||||
BeginPlay();
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::DeactivationBroadcast_Implementation() {
|
||||
if (!HasAuthority()) {
|
||||
OnDeactivated();
|
||||
this->DeactivateToPool();
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::LifeSpanExpired() {
|
||||
Deactivate();
|
||||
}
|
||||
|
||||
void AEBBullet::DeactivateToPool() {
|
||||
AEBBullet* Pool = Cast<AEBBullet>(GetClass()->GetDefaultObject());
|
||||
|
||||
if (Pool && EnablePooling) {
|
||||
SetActorHiddenInGame(true);
|
||||
SetActorTickEnabled(false);
|
||||
Pool->Pooled.Add(this);
|
||||
EndPlay(EEndPlayReason::RemovedFromWorld);
|
||||
|
||||
if (Pool->Pooled.Num() > MaxPoolSize) {
|
||||
AEBBullet* Oldest = (Pool->Pooled[0].Get());
|
||||
Pool->Pooled.RemoveAtSwap(0,EAllowShrinking::Yes);
|
||||
if (Oldest) { Oldest->Destroy(); }
|
||||
}
|
||||
|
||||
#ifdef WITH_EDITOR
|
||||
if (DebugPooling) {
|
||||
GEngine->AddOnScreenDebugMessage(2, 2, FColor::White, FString("Bullet pooled: ") + FString::FromInt(Pool->Pooled.Num()));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2020 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBarrel.h"
|
||||
#include "EBBullet.h"
|
||||
|
||||
void UEBBarrel::PredictHit(bool& Hit, FHitResult& HitResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray<FVector>& Trajectory, TSubclassOf<class AEBBullet> BulletClass, TArray<AActor*> IgnoredActors, float MaxTime, float Step) const {
|
||||
FVector StartLocation = GetComponentLocation();
|
||||
FVector AimDirection = GetComponentQuat().GetForwardVector();
|
||||
PredictHitFromLocation(Hit, HitResult, HitLocation, HitTime, HitActor, Trajectory, BulletClass, StartLocation, AimDirection, IgnoredActors, MaxTime, Step);
|
||||
}
|
||||
|
||||
void UEBBarrel::PredictHitFromLocation(bool &Hit, FHitResult& HitResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray<FVector>& Trajectory, TSubclassOf<class AEBBullet> BulletClass, FVector StartLocation, FVector AimDirection, TArray<AActor*> IgnoredActors, float MaxTime, float Step) const{
|
||||
if (!BulletClass->IsValidLowLevel()) {
|
||||
UE_LOG(LogTemp, Warning, TEXT("PredictHit - invalid bullet class"));
|
||||
return;
|
||||
}
|
||||
|
||||
float Time = 0;
|
||||
Trajectory = TArray<FVector>();
|
||||
|
||||
FVector CurrentLocation = StartLocation;
|
||||
AEBBullet* Bullet = Cast<AEBBullet>(BulletClass->GetDefaultObject());
|
||||
FVector Velocity = AimDirection.GetSafeNormal()*(FMath::Lerp(MuzzleVelocityMultiplierMin, MuzzleVelocityMultiplierMax, 0.5)*FMath::Lerp(Bullet->MuzzleVelocityMin, Bullet->MuzzleVelocityMax, 0.5));
|
||||
|
||||
UPrimitiveComponent* Parent = Cast<UPrimitiveComponent>(GetAttachParent());
|
||||
|
||||
Velocity += AdditionalVelocity;
|
||||
|
||||
if (Parent != nullptr) {
|
||||
if (Parent->IsSimulatingPhysics()) {
|
||||
Velocity += Parent->GetPhysicsLinearVelocityAtPoint(CurrentLocation)*InheritVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
while (Time < MaxTime) {
|
||||
FVector PreviousVelocity = Velocity;
|
||||
Velocity = Bullet->UpdateVelocity(GetWorld(), CurrentLocation, Velocity, Step);
|
||||
Hit = UEBBarrel::PredictTrace(GetWorld(), Bullet, CurrentLocation, CurrentLocation + FMath::Lerp(PreviousVelocity, Velocity, 0.5f)*Step, HitResult, IgnoredActors);
|
||||
if (Hit) {
|
||||
Trajectory.Add(HitResult.Location);
|
||||
HitTime = Time+(HitResult.Time*Step);
|
||||
HitActor = HitResult.GetActor();
|
||||
HitLocation = HitResult.Location;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
Trajectory.Add(CurrentLocation);
|
||||
CurrentLocation += FMath::Lerp(PreviousVelocity, Velocity, 0.5f)*Step;
|
||||
Time += Step;
|
||||
}
|
||||
}
|
||||
|
||||
Hit = false;
|
||||
HitTime = MaxTime;
|
||||
HitLocation = CurrentLocation;
|
||||
HitActor = nullptr;
|
||||
}
|
||||
|
||||
bool UEBBarrel::PredictTrace(UWorld* World, AEBBullet* Bullet, FVector Start, FVector End, FHitResult &HitResult, TArray<AActor*> IgnoredActors) const {
|
||||
|
||||
FCollisionResponseParams ResponseParams;
|
||||
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.bTraceComplex = Bullet->TraceComplex;
|
||||
QueryParams.bReturnPhysicalMaterial = true;
|
||||
|
||||
if (Bullet->SafeLaunch) {
|
||||
QueryParams.AddIgnoredActor(GetOwner());
|
||||
}
|
||||
|
||||
QueryParams.AddIgnoredActors(IgnoredActors);
|
||||
|
||||
return World->LineTraceSingleByChannel(HitResult, Start, End, Bullet->TraceChannel, QueryParams, ResponseParams);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBarrel.h"
|
||||
#include "EBBullet.h"
|
||||
|
||||
TArray<TSubclassOf<class AEBBullet>> UEBBarrel::GetAmmo(bool CountChambered) const {
|
||||
if (!CountChambered || ChamberedBullet == nullptr) {
|
||||
return Ammo;
|
||||
}
|
||||
else {
|
||||
TArray<TSubclassOf<class AEBBullet>> RetAmmo;
|
||||
RetAmmo.Add(ChamberedBullet);
|
||||
RetAmmo.Append(Ammo);
|
||||
return RetAmmo;
|
||||
};
|
||||
};
|
||||
|
||||
int UEBBarrel::GetAmmoCount(bool CountChambered) const {
|
||||
|
||||
int remainingAmmo;
|
||||
if (CycleAmmo) {
|
||||
remainingAmmo = CycleAmmoCount;
|
||||
}
|
||||
else {
|
||||
remainingAmmo = Ammo.Num();
|
||||
};
|
||||
|
||||
if (CountChambered) {
|
||||
if (ChamberedBullet != nullptr) {
|
||||
remainingAmmo++;
|
||||
};
|
||||
};
|
||||
|
||||
return remainingAmmo;
|
||||
};
|
||||
|
||||
void UEBBarrel::SetAmmo(int Count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray<TSubclassOf<class AEBBullet>>& NewAmmo) {
|
||||
Ammo = NewAmmo;
|
||||
|
||||
CycleAmmoCount = Count;
|
||||
|
||||
if (UnloadChambered) {
|
||||
ChamberedBullet = nullptr;
|
||||
};
|
||||
|
||||
if (CancelShooting) {
|
||||
BurstRemaining = 0;
|
||||
Shooting = false;
|
||||
};
|
||||
|
||||
if (ManualCharge) {
|
||||
LoadNext = false;
|
||||
};
|
||||
};
|
||||
|
||||
void UEBBarrel::Charge_Implementation() {
|
||||
LoadNext = true;
|
||||
};
|
||||
|
||||
bool UEBBarrel::Charge_Validate() {
|
||||
return true;
|
||||
};
|
||||
|
||||
void UEBBarrel::UnloadChambered_Implementation(bool ManualCharge) {
|
||||
ChamberedBullet = nullptr;
|
||||
|
||||
if (ManualCharge) {
|
||||
LoadNext = false;
|
||||
};
|
||||
};
|
||||
|
||||
bool UEBBarrel::UnloadChambered_Validate(bool ManualCharge) {
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2020 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBullet.h"
|
||||
|
||||
TArray<AActor*>AEBBullet::GetSafeLaunchIgnoredActors(AActor* BulletOwner) const{
|
||||
TArray<AActor*> Results = SafeLaunchIgnoredActors;
|
||||
|
||||
Results.Add(BulletOwner);
|
||||
|
||||
if (SafeLaunchIgnoreAttachParent && BulletOwner) {
|
||||
AActor* AttachedRoot = BulletOwner;
|
||||
|
||||
while (true) { //find attachment root
|
||||
AActor* AttachedTo;
|
||||
AttachedTo = AttachedRoot->GetAttachParentActor();
|
||||
|
||||
if (AttachedTo) {
|
||||
Results.Add(AttachedTo);
|
||||
AttachedRoot = AttachedTo;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
Results.Add(AttachedRoot);
|
||||
if (SafeLaunchIgnoreAllAttached) Results.Append(GetAttachedActorsRecursive(AttachedRoot));
|
||||
}
|
||||
|
||||
return Results;
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBullet.h"
|
||||
|
||||
float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEnumAsByte<ECollisionChannel> CollisionChannel) {
|
||||
|
||||
bool Hit;
|
||||
FHitResult HitResult;
|
||||
TArray<FHitResult> Results;
|
||||
|
||||
FCollisionResponseParams ResponseParameters;
|
||||
|
||||
FCollisionQueryParams CollisionParameters;
|
||||
CollisionParameters.bTraceComplex = TraceComplex;
|
||||
CollisionParameters.bReturnPhysicalMaterial = true;
|
||||
CollisionParameters.AddIgnoredActor(this);
|
||||
CollisionParameters.AddIgnoredActors(IgnoredActors);
|
||||
CollisionParameters.bReturnFaceIndex = true;
|
||||
|
||||
if (OwnerSafe) {
|
||||
CollisionParameters.AddIgnoredActors(GetSafeLaunchIgnoredActors(GetOwner()));
|
||||
}
|
||||
|
||||
FVector TraceDistance = (PreviousVelocity + Velocity)*0.5*delta;
|
||||
|
||||
GetWorld()->LineTraceMultiByChannel(Results, start, start + TraceDistance, CollisionChannel, CollisionParameters, ResponseParameters);
|
||||
if (Results.Num() > 0) {
|
||||
HitResult = FilterHits(Results, Hit);
|
||||
}
|
||||
else { Hit = false; }
|
||||
|
||||
if (Hit) {
|
||||
//Reduce velocity
|
||||
Velocity = FMath::Lerp(PreviousVelocity, Velocity, HitResult.Time);
|
||||
|
||||
bool Ricochet = false;
|
||||
bool Penetration = false;
|
||||
FVector exitLoc;
|
||||
FVector exitNormal;
|
||||
FVector NewVelocity = Velocity;
|
||||
|
||||
//material mods
|
||||
bool neverPenetrate = false;
|
||||
bool neverRicochet = false;
|
||||
float penDepthMultiplier = 1.0f;
|
||||
float penNormalization = PenetrationNormalization;
|
||||
float penNormalizationGrazing = PenetrationNormalizationGrazing;
|
||||
float penEnterSpread = PenetrationEntryAngleSpread;
|
||||
float penExitSpread = PenetrationExitAngleSpread;
|
||||
float ricProbMultiplier = 1.0f;
|
||||
float ricRestitution = RicochetRestitution;
|
||||
float ricFriction = RicochetFriction;
|
||||
float ricSpread = RicochetSpread;
|
||||
EPenTraceType PenTraceType = DefaultPenTraceType;
|
||||
|
||||
UPhysicalMaterial* PhysMaterial = HitResult.PhysMaterial.Get();
|
||||
|
||||
|
||||
|
||||
if (PhysMaterial) {
|
||||
//material response modifiers
|
||||
if (MaterialResponseMap != nullptr) {
|
||||
FEBMaterialResponseMapEntry* ResponseEntry = MaterialResponseMap->Map.Find(PhysMaterial);
|
||||
if (ResponseEntry != nullptr) {
|
||||
neverPenetrate = ResponseEntry->NeverPenetrate;
|
||||
neverRicochet = ResponseEntry->NeverRicochet;
|
||||
PenTraceType = ResponseEntry->PenTraceType;
|
||||
|
||||
penDepthMultiplier = ResponseEntry->PenetrationDepthMultiplier;
|
||||
penNormalization = PenetrationNormalization + ResponseEntry->PenetrationNormalization;
|
||||
penNormalizationGrazing = PenetrationNormalizationGrazing + ResponseEntry->PenetrationNormalizationGrazing;
|
||||
penEnterSpread = PenetrationEntryAngleSpread + ResponseEntry->PenetrationEntryAngleSpread;
|
||||
penExitSpread = PenetrationExitAngleSpread + ResponseEntry->PenetrationExitAngleSpread;
|
||||
|
||||
ricProbMultiplier = ResponseEntry->RicochetProbabilityMultiplier;
|
||||
ricRestitution = FMath::Lerp(RicochetRestitution, ResponseEntry->RicochetRestitution, ResponseEntry->RicochetRestitutionInfluence);
|
||||
ricFriction = FMath::Lerp(RicochetFriction, ResponseEntry->RicochetFriction, ResponseEntry->RicochetFrictionInfluence);
|
||||
ricSpread = RicochetSpread + ResponseEntry->RicochetSpread;
|
||||
}
|
||||
}
|
||||
|
||||
if (MaterialDensityControlsPenetrationDepth) {
|
||||
penDepthMultiplier /= PhysMaterial->Density;
|
||||
}
|
||||
|
||||
if (MaterialRestitutionControlsRicochet) {
|
||||
RicochetRestitution *= PhysMaterial->Restitution;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float dot = FVector::DotProduct(Velocity.GetSafeNormal(), HitResult.Normal) + 1.0f;
|
||||
FVector cross = FVector::CrossProduct(Velocity.GetSafeNormal(), HitResult.Normal);
|
||||
FVector flat = HitResult.Normal.RotateAngleAxis(-90.0f, cross);
|
||||
|
||||
#ifdef WITH_EDITOR
|
||||
if (DebugEnabled) {
|
||||
FColor DebugColor = FColor::MakeRedToGreenColorFromScalar(Velocity.Size() / MuzzleVelocityMax);
|
||||
DrawDebugLine(GetWorld(), start, HitResult.Location, DebugColor, false, DebugTrailTime, 0, DebugTrailWidth);
|
||||
};
|
||||
#endif
|
||||
|
||||
float GrazingAngle = FMath::Pow(dot, GrazingAngleExponent);
|
||||
FVector PenetrationVector = RandomStream.VRandCone(Velocity, penEnterSpread);
|
||||
PenetrationVector = FMath::Lerp(PenetrationVector, -HitResult.Normal, FMath::Lerp(penNormalization, penNormalizationGrazing, GrazingAngle));
|
||||
float PenetrationDistance = FMath::Lerp(MinPenetration, MaxPenetration, RandomStream.FRand()) * FMath::Pow((Velocity.Size() / ((MuzzleVelocityMin + MuzzleVelocityMax) * 0.5f)), 2.0f) * penDepthMultiplier;
|
||||
float PenetrationDepth = -FVector::DotProduct(PenetrationVector, HitResult.Normal) * PenetrationDistance;
|
||||
|
||||
float BlockTIme = 1.0f;
|
||||
|
||||
if (PenetrationDistance > 0.0f) {
|
||||
if (!neverPenetrate) {
|
||||
BlockTIme = PenetrationTrace(HitResult.Location - (HitResult.Normal * CollisionMargin), HitResult.Location + PenetrationVector * PenetrationDistance, HitResult.Component, PenTraceType, CollisionChannel, exitLoc, exitNormal);
|
||||
}
|
||||
}
|
||||
|
||||
if (BlockTIme >= 0.999999f) {
|
||||
|
||||
//no pen
|
||||
SetActorLocation(HitResult.Location + HitResult.Normal * CollisionMargin);
|
||||
|
||||
float ricThreshold = 1.0f;
|
||||
if (SpeedControlsRicochetProbability) { ricThreshold *= Velocity.Size() / MuzzleVelocityMax; };
|
||||
|
||||
if (!neverRicochet && RandomStream.FRand() * ricThreshold < FMath::Lerp(RicochetProbability * ricProbMultiplier, RicochetProbabilityGrazing * ricProbMultiplier, GrazingAngle)) {
|
||||
//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();
|
||||
Ricochet = true;
|
||||
OwnerSafe = false;
|
||||
}
|
||||
else {
|
||||
//stopped
|
||||
NewVelocity = FVector(0, 0, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//penetration
|
||||
float RemainingEnergy = FMath::Pow(1.0f - BlockTIme, 2.0f);
|
||||
SetActorLocation(exitLoc + exitNormal * CollisionMargin);
|
||||
NewVelocity = RandomStream.VRandCone(PenetrationVector, penExitSpread * (1.0f - RemainingEnergy));
|
||||
NewVelocity = FMath::Lerp(NewVelocity, Velocity.GetSafeNormal(), RemainingEnergy);
|
||||
NewVelocity *= RemainingEnergy * Velocity.Size();
|
||||
Penetration = true;
|
||||
OwnerSafe = false;
|
||||
}
|
||||
|
||||
|
||||
//response
|
||||
FVector Impulse = (Velocity - NewVelocity) * Mass * ImpulseMultiplier;
|
||||
|
||||
if (AddImpulse && HitResult.Component->IsSimulatingPhysics()) {
|
||||
HitResult.Component->AddImpulseAtLocation(Impulse, HitResult.Location, HitResult.BoneName);
|
||||
}
|
||||
|
||||
// New Ballistic Impact System Integration
|
||||
if (UseNewImpactSystem && BallisticImpactComponent && BulletPropertiesAsset) {
|
||||
// Calculate impact using new system
|
||||
float NewPenetrationDepth;
|
||||
bool bNewDidPenetrate;
|
||||
FVector NewExitLocation;
|
||||
|
||||
FVector NewExitLoc = BallisticImpactComponent->CalculateBallisticImpact(
|
||||
HitResult.Location,
|
||||
Velocity,
|
||||
BulletPropertiesAsset->BulletProperties,
|
||||
PhysMaterial,
|
||||
NewPenetrationDepth,
|
||||
bNewDidPenetrate,
|
||||
NewExitLocation
|
||||
);
|
||||
|
||||
// Calculate ricochet using new system
|
||||
float NewEnergyRetained;
|
||||
bool bNewDidRicochet;
|
||||
|
||||
FVector NewRicochetVelocity = BallisticImpactComponent->CalculateRicochet(
|
||||
HitResult.Location,
|
||||
HitResult.Normal,
|
||||
Velocity,
|
||||
BulletPropertiesAsset->BulletProperties,
|
||||
PhysMaterial,
|
||||
NewEnergyRetained,
|
||||
bNewDidRicochet
|
||||
);
|
||||
|
||||
// Override legacy system results with new system
|
||||
if (bNewDidPenetrate) {
|
||||
Penetration = true;
|
||||
PenetrationDepth = NewPenetrationDepth;
|
||||
exitLoc = NewExitLocation;
|
||||
}
|
||||
|
||||
if (bNewDidRicochet) {
|
||||
Ricochet = true;
|
||||
NewVelocity = NewRicochetVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
//impact actual
|
||||
if (HasAuthority()) {
|
||||
OnImpact(Ricochet, Penetration, HitResult.Location, Velocity, HitResult.Normal, GetActorLocation(), NewVelocity, Impulse, PenetrationDepth, HitResult.GetActor(), HitResult.Component.Get(), HitResult.BoneName, PhysMaterial, HitResult);
|
||||
}
|
||||
else {
|
||||
OnNetPredictedImpact(Ricochet, Penetration, HitResult.Location, Velocity, HitResult.Normal, GetActorLocation(), NewVelocity, Impulse, PenetrationDepth, HitResult.GetActor(), HitResult.Component.Get(), HitResult.BoneName, PhysMaterial, HitResult);
|
||||
}
|
||||
|
||||
Velocity = NewVelocity;
|
||||
|
||||
if ((Velocity.Size() < DespawnVelocity) || (!Ricochet && !Penetration && (DespawnVelocity>0.0f))){
|
||||
Deactivate();
|
||||
}
|
||||
CanRetrace = false;
|
||||
}
|
||||
else {
|
||||
//prepare for time travel
|
||||
if (Retrace) {
|
||||
CanRetrace = true;
|
||||
LastTraceStart = start;
|
||||
LastTraceDelta = delta;
|
||||
LastTracePrevVelocity = PreviousVelocity;
|
||||
LastTraceVelocity = Velocity;
|
||||
}
|
||||
|
||||
SetActorLocation(start + TraceDistance);
|
||||
HitResult.Time = 1.0f;
|
||||
|
||||
OnTrace(start, GetActorLocation());
|
||||
|
||||
#ifdef WITH_EDITOR
|
||||
if (DebugEnabled) {
|
||||
FLinearColor Color = GetDebugColor(Velocity.Size() / ((MuzzleVelocityMin + MuzzleVelocityMax)*0.5f));
|
||||
DrawDebugLine(GetWorld(), start, start + TraceDistance, Color.ToFColor(true), false, DebugTrailTime, 0, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return delta*(1.0f - HitResult.Time);
|
||||
}
|
||||
|
||||
TArray<AActor*> AEBBullet::GetAttachedActorsRecursive(AActor* Actor, uint16 Depth, TArray<AActor*> VisitedActors) const {
|
||||
//TODO: limit depth
|
||||
TArray<AActor*> Attached;
|
||||
Actor->GetAttachedActors(Attached);
|
||||
|
||||
TArray<AActor*> AttachedRecursive;
|
||||
for (AActor* ActorRecursive : Attached) { // Skip already visited actors to avoid infinite recursion
|
||||
if (!VisitedActors.Contains(ActorRecursive)) {
|
||||
VisitedActors.Add(ActorRecursive);
|
||||
AttachedRecursive += GetAttachedActorsRecursive(ActorRecursive, Depth+1, VisitedActors);
|
||||
VisitedActors.Remove(ActorRecursive); // Remove from visited actors to allow other branches to visit it
|
||||
}
|
||||
}
|
||||
|
||||
Attached += AttachedRecursive;
|
||||
return Attached;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "PhysicalMaterials/PhysicalMaterial.h"
|
||||
#include "EBBulletProperties.h"
|
||||
#include "EBMaterialResponseMap.h"
|
||||
#include "EBBallisticImpactComponent.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FOnBallisticImpact,
|
||||
FVector, ImpactLocation,
|
||||
FVector, ImpactNormal,
|
||||
UPhysicalMaterial*, HitMaterial,
|
||||
float, PenetrationDepth,
|
||||
bool, bDidPenetrate);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnBallisticRicochet,
|
||||
FVector, RicochetLocation,
|
||||
FVector, RicochetDirection,
|
||||
UPhysicalMaterial*, HitMaterial,
|
||||
float, EnergyRetained);
|
||||
|
||||
UCLASS(ClassGroup=(Ballistics), meta=(BlueprintSpawnableComponent))
|
||||
class EASYBALLISTICS_API UEBBallisticImpactComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UEBBallisticImpactComponent();
|
||||
|
||||
// Events
|
||||
UPROPERTY(BlueprintAssignable, Category = "Ballistics Events")
|
||||
FOnBallisticImpact OnBallisticImpact;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Ballistics Events")
|
||||
FOnBallisticRicochet OnBallisticRicochet;
|
||||
|
||||
// Configuration
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
|
||||
UEBMaterialResponseMap* MaterialResponseMap;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
|
||||
bool bEnableBallisticCalculations = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
|
||||
bool bUseMathematicalPenetration = false;
|
||||
|
||||
// Blueprint callable functions
|
||||
UFUNCTION(BlueprintCallable, Category = "Ballistics")
|
||||
FVector CalculateBallisticImpact(
|
||||
const FVector& ImpactLocation,
|
||||
const FVector& ProjectileVelocity,
|
||||
const FMathematicalBulletProperties& BulletProperties,
|
||||
UPhysicalMaterial* HitMaterial,
|
||||
float& OutPenetrationDepth,
|
||||
bool& bOutDidPenetrate,
|
||||
FVector& OutExitLocation
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Ballistics")
|
||||
FVector CalculateRicochet(
|
||||
const FVector& ImpactLocation,
|
||||
const FVector& ImpactNormal,
|
||||
const FVector& ProjectileVelocity,
|
||||
const FMathematicalBulletProperties& BulletProperties,
|
||||
UPhysicalMaterial* HitMaterial,
|
||||
float& OutEnergyRetained,
|
||||
bool& bOutDidRicochet
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Ballistics")
|
||||
UEBMaterialPropertiesAsset* GetMaterialProperties(UPhysicalMaterial* PhysicalMaterial);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Ballistics")
|
||||
FEBMaterialResponseMapEntry GetMaterialResponse(UPhysicalMaterial* PhysicalMaterial);
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
private:
|
||||
FVector CalculatePenetrationVector(
|
||||
const FVector& ImpactLocation,
|
||||
const FVector& ImpactNormal,
|
||||
const FVector& ProjectileVelocity,
|
||||
float PenetrationDepth
|
||||
);
|
||||
|
||||
float CalculateImpactAngle(const FVector& ProjectileVelocity, const FVector& SurfaceNormal);
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2020 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
#include "EBBullet.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))
|
||||
class EASYBALLISTICS_API UEBBarrel : public UPrimitiveComponent
|
||||
{
|
||||
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets default values for this component's properties
|
||||
UEBBarrel();
|
||||
|
||||
// Called every frame
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugArrowSize = 100.0f;
|
||||
|
||||
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);
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Additional maximum spread, in radians, applied on top of bullet spread", ClampMin = "0")) float Spread=0.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Additional Spread bias, higher is more accurate on average", ClampMin = "0")) float SpreadBias = 0.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Minimum of random multiplier applied to bullet muzzle velocity")) float MuzzleVelocityMultiplierMin = 1.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Maximum of random multiplier applied to bullet muzzle velocity")) float MuzzleVelocityMultiplierMax = 1.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Minimum fire rate, rounds per second")) float FireRateMin = 1.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Maximum fire rate, rounds per second, set to same number as FireRateMin to disable randomization")) float FireRateMax = 1.0f;
|
||||
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Weapon") EFireMode FireMode = EFireMode::FM_Auto;
|
||||
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Weapon") bool ShootingBlocked;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Number of rounds auto fired in burst mode")) int BurstCount = 3;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Minimum time between bursts")) float BurstCooldown = 0.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Automatically spin up gatling when trigger is being held down")) bool GatlingAutoSpool = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon") float GatlingSpoolUpTime = 1.0f;
|
||||
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;
|
||||
|
||||
UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") TSubclassOf<class AEBBullet> ChamberedBullet;
|
||||
UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") bool Shooting;
|
||||
UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") bool Spooling = false;
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Weapon") float GatlingRPS = 0.0f;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "WeaponState") bool LoadNext=true;
|
||||
UPROPERTY(BlueprintReadWrite, Category = "WeaponState") float Cooldown;
|
||||
UPROPERTY(BlueprintReadWrite, Category = "WeaponState") int BurstRemaining;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") bool ReplicateVariables=true;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") bool ReplicateShotFiredEvents = true;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") bool ClientSideAim=false;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") float ClientAimUpdateFrequency = 15.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") float ClientAimDistanceLimit = 200.0f;
|
||||
|
||||
FRandomStream RandomStream;
|
||||
|
||||
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(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);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IgnoredActors"), Category = "Prediction") void PredictHit(bool& Hit, FHitResult& TraceResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray<FVector>& Trajectory, TSubclassOf<class AEBBullet> BulletClass, TArray<AActor*>IgnoredActors, float MaxTime = 10.0f, float Step = 0.1f) const;
|
||||
UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IgnoredActors"), Category = "Prediction") void PredictHitFromLocation(bool &Hit, FHitResult& TraceResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray<FVector>& Trajectory, TSubclassOf<class AEBBullet> BulletClass, FVector StartLocation, FVector AimDirection, TArray<AActor*>IgnoredActors, float MaxTime = 10.0f, float Step = 0.1f) const;
|
||||
UFUNCTION(BlueprintCallable, Category = "Prediction") void CalculateAimDirection(TSubclassOf<class AEBBullet> BulletClass, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime = 10.0f, float Step = 0.1f, int NumIterations = 4) const;
|
||||
UFUNCTION(BlueprintCallable, Category = "Prediction") void CalculateAimDirectionFromLocation(TSubclassOf<class AEBBullet> BulletClass, FVector StartLocation, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime = 10.0f, float Step=0.1f, int NumIterations = 4) const;
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Events") void InitialBulletTransform(FVector InLocation, FVector InDirection, FVector& OutLocation, FVector& OutDirection);
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Events") void ApplyRecoil(UPrimitiveComponent* Component, FVector InLocation, FVector Impulse);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FBeforeShotFired);
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events")
|
||||
FBeforeShotFired BeforeShotFired;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FShotFired);
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events")
|
||||
FShotFired ShotFired;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAmmoDepleted);
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events")
|
||||
FAmmoDepleted AmmoDepleted;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FReadyToShoot);
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events")
|
||||
FReadyToShoot ReadyToShoot;
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
|
||||
virtual bool IsZeroExtent() const override { return false; };
|
||||
virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
void SpawnBullet(AActor* Owner, FVector LocalLocation, FVector LocalAim);
|
||||
|
||||
UFUNCTION(Server, Unreliable, WithValidation) void ClientAim(FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim);
|
||||
UFUNCTION(Server, Reliable, WithValidation) void ShootRep(bool Trigger);
|
||||
UFUNCTION(Server, Reliable, WithValidation) void ShootRepCSA(bool Trigger, FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim);
|
||||
|
||||
UFUNCTION(NetMulticast, Reliable)
|
||||
void ShotFiredMulticast();
|
||||
|
||||
FVector Aim;
|
||||
FVector Location;
|
||||
bool RemoteAimReceived;
|
||||
float TimeSinceAimUpdate;
|
||||
bool PredictTrace(UWorld* World, AEBBullet* Bullet, FVector Start, FVector End, FHitResult &HitResult, TArray<AActor*> IgnoredActors) const;
|
||||
};
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Curves/CurveFloat.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
|
||||
#include "EBMaterialResponseMap.h"
|
||||
#include "EBBulletProperties.h"
|
||||
#include "EBMathematicalBallistics.h"
|
||||
#include "EBBallisticImpactComponent.h"
|
||||
|
||||
#include "EBBullet.generated.h"
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EEBAtmosphereType : uint8
|
||||
{
|
||||
AT_Constant UMETA(DisplayName = "Constant"),
|
||||
AT_Curve UMETA(DisplayName = "Density Curve"),
|
||||
AT_Earth UMETA(DisplayName = "Earth/IGL")
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class EASYBALLISTICS_API AEBBullet : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets default values for this actor's properties
|
||||
AEBBullet();
|
||||
|
||||
UPROPERTY(Replicated, BlueprintReadWrite, Category = "State") FVector Velocity;
|
||||
UPROPERTY(Replicated, BlueprintReadWrite, Category = "State") FRandomStream RandomStream;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "State") bool OwnerSafe=false;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") bool DebugEnabled;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugTrailTime=1.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugTrailWidth=0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") FLinearColor DebugTrailColorFast = FLinearColor(0, 1, 0, 1);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") FLinearColor DebugTrailColorSlow = FLinearColor(1, 0, 0, 1);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") bool DebugPooling;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") FVector Wind;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Select atmosphere model")) EEBAtmosphereType AtmosphereType;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World", meta = (ToolTip = "Air Density at sea level - in KG/m^3", ClampMin = "0")) float SeaLevelAirDensity = 1.21;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World", meta = (ToolTip = "in cm/s", ClampMin = "0")) float SeaLevelSpeedOfSound = 34300;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World", meta = (ToolTip = "Used for Density Curve atmosphere model")) UCurveFloat* AirDensityCurve;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") bool SpeedOfSoundVariesWithAltitude = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") UCurveFloat* SpeedOfSoundCurve;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") float WorldScale = 1.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Atmosphere pressure at 0,0,0 - in millibars", ClampMin = "0")) float SeaLevelAirPressure = 1012.5f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Atmosphere Temperature at 0,0,0 - in degrees C")) float SeaLevelAirTemperature = 20.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Temperature Decrease With Altitude, degrees per meter")) float TemperatureLapseRate = 0.00649f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Altitude at which temperature stops decreasing, in meters")) float TropopauseAltitude = 11000.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Specific Gas Constant, dry air = 287.058", ClampMin = "0")) float SpecificGasConstant = 287.058;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "World Origin Location")) FVector WorldCenterLocation = FVector(0, 0, 0);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Use spherical planet model to get altitude")) bool SphericalAltitude = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Planet radius, in Unreal units", EditCondition = "SphericalAltitude", ClampMin = "0")) float SeaLevelRadius = 637100000.0f;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") bool OverrideGravity = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") FVector Gravity = FVector(0,0,-980);
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch") bool SafeLaunch = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch", Meta = (EditCondition = "SafeLaunch")) bool SafeLaunchIgnoreAttachParent = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch", Meta = (EditCondition = "SafeLaunchIgnoreAttachParent")) bool SafeLaunchIgnoreAllAttached = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch", Meta = (EditCondition = "SafeLaunch", ClampMin = "0")) float SafeDelay = 1.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch", Meta = (EditCondition = "SafeLaunch")) TArray<AActor*> SafeLaunchIgnoredActors;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun") bool Shotgun=false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun", meta = (EditCondition = "Shotgun")) int ShotCount=10;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun", meta = (EditCondition = "Shotgun")) float ShotSpread=0.01;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun", meta = (EditCondition = "Shotgun")) float ShotVelocitySpread = 0.01;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight") float MuzzleVelocityMin = 100000.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight") float MuzzleVelocityMax = 100000.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight", meta = (ToolTip = "Maximum bullet spread, in radians", ClampMin = "0")) float Spread = 0.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight", meta = (ToolTip = "Spread bias, higher is more accurate on average", ClampMin = "0")) float SpreadBias = 0.0f;
|
||||
// Physics Mode Toggle
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Physics Mode", meta = (ToolTip = "Toggle between artistic (manual) and mathematical (realistic) physics calculations"))
|
||||
bool UseMathematicalPhysics = false;
|
||||
|
||||
// Mathematical Properties (used when UseMathematicalPhysics is true)
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mathematical Properties", meta = (EditCondition = "UseMathematicalPhysics", ToolTip = "Bullet properties asset for mathematical calculations"))
|
||||
UEBBulletPropertiesAsset* BulletPropertiesAsset;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mathematical Properties", meta = (EditCondition = "UseMathematicalPhysics", ToolTip = "Material properties asset for mathematical calculations"))
|
||||
UEBMaterialPropertiesAsset* MaterialPropertiesAsset;
|
||||
|
||||
// Artistic Properties (used when UseMathematicalPhysics is false)
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Properties", meta = (EditCondition = "!UseMathematicalPhysics", ToolTip = "Bullet mass in kg"))
|
||||
float Mass = 0.005;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Properties", meta = (EditCondition = "!UseMathematicalPhysics", ToolTip = "Bullet diameter in cm"))
|
||||
float Diameter = 0.556;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Properties", meta = (EditCondition = "!UseMathematicalPhysics", ToolTip = "Form factor for drag calculations"))
|
||||
float FormFactor = 1.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Properties", meta = (EditCondition = "!UseMathematicalPhysics", ToolTip = "Drag curve vs Mach number"))
|
||||
UCurveFloat* MachDragCurve;
|
||||
|
||||
// Artistic Impact Properties (used when UseMathematicalPhysics is false)
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float GrazingAngleExponent = 2.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float MinPenetration = 10.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float MaxPenetration = 20.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float PenetrationNormalization = 0.5;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float PenetrationNormalizationGrazing = 0.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float PenetrationEntryAngleSpread = 0.1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float PenetrationExitAngleSpread = 0.1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float RicochetProbability = 0.1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float RicochetProbabilityGrazing = 1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float RicochetRestitution = 0.1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float RicochetFriction = 0.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float RicochetSpread = 0.1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
bool SpeedControlsRicochetProbability = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
bool AddImpulse = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics"))
|
||||
float ImpulseMultiplier = 1.0;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact") EPenTraceType DefaultPenTraceType = EPenTraceType::PT_BackTrace;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact") UEBMaterialResponseMap* MaterialResponseMap;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact") bool MaterialDensityControlsPenetrationDepth = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact") bool MaterialRestitutionControlsRicochet = true;
|
||||
|
||||
// New Ballistic Impact System
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Impact")
|
||||
UEBBallisticImpactComponent* BallisticImpactComponent;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact")
|
||||
bool UseNewImpactSystem = false;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Replication") bool ReliableReplication = false;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision", meta = (ToolTip = "Allow components to collide, intended for use with trigger volumes. Do not use for actual collisions.")) bool AllowComponentCollisions = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") TEnumAsByte<ECollisionChannel> TraceChannel;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") bool TraceComplex;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") float CollisionMargin=1.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision", meta = (ToolTip = "Bullets with lower velocity will automatically despawn on impact, never despawn if set to zero or negative")) float DespawnVelocity=100.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") TArray<AActor*> IgnoredActors;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation", meta = (ToolTip = "Spawned bullet performs first trace immediately, instead of waiting for next simulation step")) bool DoFirstStepImmediately = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation", meta = (EditCondition = "DoFirstStepImmediately")) bool RandomFirstStepDelta = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation") bool FixedStep = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation", meta = (EditCondition = "FixedStep", ClampMin = "0")) float FixedStepSeconds = 0.1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation") int MaxTracesPerStep = 8;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Retrace") bool Retrace = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Retrace") bool RetraceOnAnotherChannel = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Retrace", meta=(EditCondition="RetraceOnAnotherChannel")) TEnumAsByte<ECollisionChannel> RetraceChannel;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Rotation") bool RotateActor = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Rotation") bool RotateRandomRoll = true;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Pooling") bool EnablePooling = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Pooling", meta = (EditCondition = "EnablePooling")) int MaxPoolSize = 50;
|
||||
|
||||
//rebase
|
||||
virtual void ApplyWorldOffset(const FVector& InOffset, bool bWorldShift) override;
|
||||
|
||||
// Called when the game starts or when spawned
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
// Called every frame
|
||||
virtual void Tick(float DeltaSeconds) override;
|
||||
|
||||
virtual void LifeSpanExpired() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Spawn")
|
||||
static void SpawnWithExactVelocity(TSubclassOf<class AEBBullet> BulletClass, AActor* BulletOwner, APawn* BulletInstigator, FVector BulletLocation, FVector BulletVelocity);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Spawn")
|
||||
static void Spawn(TSubclassOf<class AEBBullet> BulletClass, AActor* BulletOwner, APawn* BulletInstigator, FVector BulletLocation, FVector BulletVelocity);
|
||||
|
||||
UFUNCTION(NetMulticast, Unreliable)
|
||||
void VelocityChangeBroadcast(FVector_NetQuantize NewLocation, FVector NewVelocity);
|
||||
UFUNCTION(NetMulticast, Reliable)
|
||||
void VelocityChangeBroadcastReliable(FVector_NetQuantize NewLocation, FVector NewVelocity);
|
||||
|
||||
UFUNCTION(BlueprintAuthorityOnly, BlueprintNativeEvent, Category = "EBBullet|Impact")
|
||||
void OnImpact(bool Ricochet, bool PassedThrough, FVector Location, FVector IncomingVelocity, FVector Normal, FVector ExitLocation, FVector ExitVelocity, FVector Impulse, float PenetrationDepth, AActor* Actor, USceneComponent* Component, FName BoneName, UPhysicalMaterial* PhysMaterial, FHitResult HitResult);
|
||||
|
||||
UFUNCTION(BlueprintCosmetic, BlueprintNativeEvent, Category = "EBBullet|Impact")
|
||||
void OnNetPredictedImpact(bool Ricochet, bool PassedThrough, FVector Location, FVector IncomingVelocity, FVector Normal, FVector ExitLocation, FVector ExitVelocity, FVector Impulse, float PenetrationDepth, AActor* Actor, USceneComponent* Component, FName BoneName, UPhysicalMaterial* PhysMaterial, FHitResult HitResult);
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "EBBullet|Impact")
|
||||
void OnTrace(FVector StartLocation, FVector EndLocation);
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "EBBullet|Remote")
|
||||
void OnTrajectoryUpdateReceived(FVector Location, FVector OldVelocity, FVector NewVelocity);
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|Activation")
|
||||
void OnDeactivated();
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|Flight")FVector UpdateVelocity(UWorld* World, FVector Location, FVector PreviousVelocity, float DeltaTime) const;
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|World") FVector GetWind(UWorld* World, FVector Location) const;
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|World") float GetAirDensity(UWorld* World, FVector Location) const;
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|World") float GetSpeedOfSound(UWorld* World, FVector Location) const;
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|World") bool CollisionFilter(FHitResult HitResult) const;
|
||||
|
||||
// Mathematical Physics Functions
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics")
|
||||
float GetEffectiveMass() const;
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics")
|
||||
float GetEffectiveDiameter() const;
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics")
|
||||
float GetEffectiveDragCoefficient(float MachNumber) const;
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics")
|
||||
float CalculateMathematicalPenetration(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const;
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics")
|
||||
float CalculateMathematicalRicochetProbability(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const;
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics")
|
||||
FMathematicalMaterialProperties GetMaterialProperties(UPhysicalMaterial* Material) const;
|
||||
|
||||
//pooling
|
||||
UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "EBBullet|Pooling")void Deactivate();
|
||||
|
||||
UFUNCTION(NetMulticast, Reliable)
|
||||
void ReactivationBroadcast(FVector_NetQuantize NewLocation, FVector NewVelocity, AActor* BulletOwner, APawn* BulletInstigator);
|
||||
UFUNCTION(NetMulticast, Reliable)
|
||||
void DeactivationBroadcast();
|
||||
private:
|
||||
UPROPERTY() TArray<TWeakObjectPtr<AEBBullet>> Pooled;
|
||||
static AEBBullet* GetFromPool(UWorld* World, UClass* BulletClass);
|
||||
static AEBBullet* SpawnOrReactivate(UWorld* World, TSubclassOf<class AEBBullet> BulletClass, const FTransform& Transform, FVector BulletVelocity, AActor* BulletOwner, APawn* BulletInstigator);
|
||||
void DeactivateToPool();
|
||||
|
||||
void FinishSpawning(FTransform Transform);
|
||||
|
||||
void Step(float DeltaTime);
|
||||
|
||||
float Trace(FVector start, FVector PreviousVelocity, float delta, TEnumAsByte<ECollisionChannel> channel);
|
||||
|
||||
TArray<AActor*> GetAttachedActorsRecursive(AActor* Actor, uint16 Depth = 0, TArray<AActor*> VisitedActors = TArray<AActor*>()) const;
|
||||
|
||||
float PenetrationTrace(FVector start, FVector end, TWeakObjectPtr<UPrimitiveComponent,FWeakObjectPtr> comp, EPenTraceType penType, TEnumAsByte<ECollisionChannel> channel, FVector &exitLoc, FVector &exitNormal);
|
||||
|
||||
float GetCurveValue(const UCurveFloat* curve, float in, float deflt) const;
|
||||
|
||||
float AccumulatedDelta;
|
||||
|
||||
bool CanRetrace = false;
|
||||
FVector LastTraceStart;
|
||||
float LastTraceDelta;
|
||||
FVector LastTraceVelocity;
|
||||
FVector LastTracePrevVelocity;
|
||||
|
||||
bool IsRecycled;
|
||||
|
||||
FHitResult FilterHits(TArray<FHitResult> Results, bool &hit) const;
|
||||
TArray<AActor*>GetSafeLaunchIgnoredActors(AActor* Owner) const;
|
||||
|
||||
float GetAltitude(UWorld* World, FVector Location) const;
|
||||
float GetAltitudePressure(float AltitudeMeter) const;
|
||||
float GetAltitudeTemperature(float AltitudeMeter) const;
|
||||
float GetAltitudeDensity(float AltitudeMeter) const;
|
||||
|
||||
#ifdef WITH_EDITOR
|
||||
FLinearColor GetDebugColor(float In) const{
|
||||
return FMath::Lerp(DebugTrailColorSlow, DebugTrailColorFast, In);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "EBBulletProperties.generated.h"
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EBulletType : uint8
|
||||
{
|
||||
BT_FullMetalJacket UMETA(DisplayName = "Full Metal Jacket"),
|
||||
BT_HollowPoint UMETA(DisplayName = "Hollow Point"),
|
||||
BT_SoftPoint UMETA(DisplayName = "Soft Point"),
|
||||
BT_ArmorPiercing UMETA(DisplayName = "Armor Piercing"),
|
||||
BT_ArmorPiercingIncendiary UMETA(DisplayName = "Armor Piercing Incendiary"),
|
||||
BT_Tracer UMETA(DisplayName = "Tracer"),
|
||||
BT_Match UMETA(DisplayName = "Match Grade"),
|
||||
BT_Frangible UMETA(DisplayName = "Frangible"),
|
||||
BT_LeadRoundNose UMETA(DisplayName = "Lead Round Nose"),
|
||||
BT_Wadcutter UMETA(DisplayName = "Wadcutter"),
|
||||
BT_SemiWadcutter UMETA(DisplayName = "Semi-Wadcutter"),
|
||||
BT_Custom UMETA(DisplayName = "Custom")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EBulletMaterial : uint8
|
||||
{
|
||||
BM_Lead UMETA(DisplayName = "Lead"),
|
||||
BM_LeadAntimony UMETA(DisplayName = "Lead-Antimony"),
|
||||
BM_Copper UMETA(DisplayName = "Copper"),
|
||||
BM_CopperJacket UMETA(DisplayName = "Copper Jacket"),
|
||||
BM_Brass UMETA(DisplayName = "Brass"),
|
||||
BM_Steel UMETA(DisplayName = "Steel"),
|
||||
BM_Tungsten UMETA(DisplayName = "Tungsten"),
|
||||
BM_Bismuth UMETA(DisplayName = "Bismuth"),
|
||||
BM_Zinc UMETA(DisplayName = "Zinc"),
|
||||
BM_Custom UMETA(DisplayName = "Custom")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMathematicalBulletProperties
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
// Basic Properties
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties", meta = (ToolTip = "Bullet weight in grains (1 grain = 0.0647989 grams)"))
|
||||
float GrainWeight = 55.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties", meta = (ToolTip = "Bullet diameter in inches"))
|
||||
float DiameterInches = 0.224f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties", meta = (ToolTip = "Bullet length in inches"))
|
||||
float LengthInches = 0.825f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties")
|
||||
EBulletType BulletType = EBulletType::BT_FullMetalJacket;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties")
|
||||
EBulletMaterial BulletMaterial = EBulletMaterial::BM_CopperJacket;
|
||||
|
||||
// Ballistic Coefficient
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics", meta = (ToolTip = "G1 Ballistic Coefficient"))
|
||||
float BallisticCoefficientG1 = 0.151f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics", meta = (ToolTip = "G7 Ballistic Coefficient"))
|
||||
float BallisticCoefficientG7 = 0.076f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics", meta = (ToolTip = "Use G7 model instead of G1"))
|
||||
bool UseG7Model = false;
|
||||
|
||||
// Sectional Density
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics", meta = (ToolTip = "Sectional density (calculated automatically if zero)"))
|
||||
float SectionalDensity = 0.0f;
|
||||
|
||||
// Penetration Properties
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Penetration", meta = (ToolTip = "Bullet hardness (HB - Brinell Hardness)"))
|
||||
float BulletHardness = 15.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Penetration", meta = (ToolTip = "Kinetic energy threshold for penetration (ft-lbs)"))
|
||||
float PenetrationEnergyThreshold = 58.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Penetration", meta = (ToolTip = "Expansion threshold velocity (fps)"))
|
||||
float ExpansionVelocityThreshold = 1800.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Penetration", meta = (ToolTip = "Maximum expansion diameter multiplier"))
|
||||
float MaxExpansionMultiplier = 1.5f;
|
||||
|
||||
// Constructor
|
||||
FMathematicalBulletProperties()
|
||||
{
|
||||
GrainWeight = 55.0f;
|
||||
DiameterInches = 0.224f;
|
||||
LengthInches = 0.825f;
|
||||
BulletType = EBulletType::BT_FullMetalJacket;
|
||||
BulletMaterial = EBulletMaterial::BM_CopperJacket;
|
||||
BallisticCoefficientG1 = 0.151f;
|
||||
BallisticCoefficientG7 = 0.076f;
|
||||
UseG7Model = false;
|
||||
SectionalDensity = 0.0f;
|
||||
BulletHardness = 15.0f;
|
||||
PenetrationEnergyThreshold = 58.0f;
|
||||
ExpansionVelocityThreshold = 1800.0f;
|
||||
MaxExpansionMultiplier = 1.5f;
|
||||
}
|
||||
|
||||
// Calculate sectional density if not provided
|
||||
float GetSectionalDensity() const
|
||||
{
|
||||
if (SectionalDensity > 0.0f)
|
||||
{
|
||||
return SectionalDensity;
|
||||
}
|
||||
// SD = Weight(grains) / (7000 * Diameter^2(inches))
|
||||
return GrainWeight / (7000.0f * DiameterInches * DiameterInches);
|
||||
}
|
||||
|
||||
// Calculate mass in kilograms
|
||||
float GetMassKg() const
|
||||
{
|
||||
// 1 grain = 0.0647989 grams
|
||||
return GrainWeight * 0.0647989f / 1000.0f;
|
||||
}
|
||||
|
||||
// Calculate diameter in centimeters
|
||||
float GetDiameterCm() const
|
||||
{
|
||||
// 1 inch = 2.54 cm
|
||||
return DiameterInches * 2.54f;
|
||||
}
|
||||
|
||||
// Calculate cross-sectional area in square centimeters
|
||||
float GetCrossSectionCm2() const
|
||||
{
|
||||
float radiusCm = GetDiameterCm() / 2.0f;
|
||||
return 3.14159f * radiusCm * radiusCm;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMathematicalMaterialProperties
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
// Material Properties
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Material density in g/cm³"))
|
||||
float DensityGPerCm3 = 7.85f; // Steel
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Material hardness (HB - Brinell Hardness)"))
|
||||
float MaterialHardness = 200.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Tensile strength in MPa"))
|
||||
float TensileStrengthMPa = 400.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Yield strength in MPa"))
|
||||
float YieldStrengthMPa = 250.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Modulus of elasticity in GPa"))
|
||||
float ElasticModulusGPa = 200.0f;
|
||||
|
||||
// Ballistic Properties
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties", meta = (ToolTip = "Ballistic limit velocity (fps)"))
|
||||
float BallisticLimitVelocity = 2000.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties", meta = (ToolTip = "Perforation coefficient"))
|
||||
float PerforationCoefficient = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties", meta = (ToolTip = "Energy absorption coefficient"))
|
||||
float EnergyAbsorptionCoefficient = 0.7f;
|
||||
|
||||
// Constructor with default steel properties
|
||||
FMathematicalMaterialProperties()
|
||||
{
|
||||
DensityGPerCm3 = 7.85f; // Steel
|
||||
MaterialHardness = 200.0f;
|
||||
TensileStrengthMPa = 400.0f;
|
||||
YieldStrengthMPa = 250.0f;
|
||||
ElasticModulusGPa = 200.0f;
|
||||
BallisticLimitVelocity = 2000.0f;
|
||||
PerforationCoefficient = 1.0f;
|
||||
EnergyAbsorptionCoefficient = 0.7f;
|
||||
}
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class EASYBALLISTICS_API UEBBulletPropertiesAsset : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Bullet Properties")
|
||||
FMathematicalBulletProperties BulletProperties;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description")
|
||||
FString BulletName = "5.56x45mm NATO";
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description")
|
||||
FString Manufacturer = "Generic";
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description")
|
||||
FString Description = "Standard 5.56x45mm NATO round";
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class EASYBALLISTICS_API UEBMaterialPropertiesAsset : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties")
|
||||
FMathematicalMaterialProperties MaterialProperties;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description")
|
||||
FString MaterialName = "Steel";
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description")
|
||||
FString Description = "Standard structural steel";
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "PhysicalMaterials/PhysicalMaterial.h"
|
||||
#include "EBBulletProperties.h"
|
||||
#include "EBMaterialResponseMap.generated.h"
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EPenTraceType : uint8
|
||||
{
|
||||
PT_BackTrace UMETA(DisplayName = "Back Trace"),
|
||||
PT_ByComponent UMETA(DisplayName = "By Component"),
|
||||
PT_TwoSidedGeometry UMETA(DisplayName = "Double Sided Geometry"),
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FEBMaterialResponseMapEntry {
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
// Artistic Properties
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") EPenTraceType PenTraceType = EPenTraceType::PT_BackTrace;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") bool NeverPenetrate = false;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationDepthMultiplier = 1.0f;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationNormalization = 0.0f;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationNormalizationGrazing = 0.0f;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationEntryAngleSpread = 0.0f;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationExitAngleSpread = 0.0;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") bool NeverRicochet = false;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetProbabilityMultiplier = 1.0f;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetRestitution = 0.5f;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetRestitutionInfluence = 0.0f;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetFriction = 0.5f;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetFrictionInfluence = 0.0f;
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetSpread = 0.0f;
|
||||
|
||||
// Mathematical Properties
|
||||
UPROPERTY(EditAnywhere, Category = "Mathematical Properties", meta = (ToolTip = "Use mathematical properties for this material"))
|
||||
bool UseMathematicalProperties = false;
|
||||
UPROPERTY(EditAnywhere, Category = "Mathematical Properties", meta = (EditCondition = "UseMathematicalProperties"))
|
||||
FMathematicalMaterialProperties MathematicalProperties;
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class UEBMaterialResponseMap : public UDataAsset{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, Category = "Responses") TMap<UPhysicalMaterial*, FEBMaterialResponseMapEntry> Map;
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "EBBulletProperties.h"
|
||||
#include "EBMathematicalBallistics.generated.h"
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class EASYBALLISTICS_API UEBMathematicalBallistics : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Mathematical penetration calculation using empirical formulas
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculatePenetrationDepth(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS,
|
||||
float ImpactAngleDegrees = 0.0f
|
||||
);
|
||||
|
||||
// Calculate residual velocity after penetration
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculateResidualVelocity(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS,
|
||||
float ThicknessCM,
|
||||
float ImpactAngleDegrees = 0.0f
|
||||
);
|
||||
|
||||
// Calculate ricochet probability based on impact angle and material properties
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculateRicochetProbability(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS,
|
||||
float ImpactAngleDegrees
|
||||
);
|
||||
|
||||
// Calculate bullet expansion based on velocity and material
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculateBulletExpansion(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS
|
||||
);
|
||||
|
||||
// Calculate kinetic energy
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculateKineticEnergy(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
float VelocityMPS
|
||||
);
|
||||
|
||||
// Calculate momentum
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculateMomentum(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
float VelocityMPS
|
||||
);
|
||||
|
||||
// Calculate drag coefficient from ballistic coefficient
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculateDragCoefficient(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
float MachNumber
|
||||
);
|
||||
|
||||
// Calculate Taylor-Hopkinson perforation limit
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculateTaylorHopkinsonLimit(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps
|
||||
);
|
||||
|
||||
// Calculate Recht-Ipson perforation velocity
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculateRechtIpsonVelocity(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float ThicknessCM
|
||||
);
|
||||
|
||||
// Calculate critical angle for ricochet
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float CalculateCriticalRicochetAngle(
|
||||
const FMathematicalBulletProperties& BulletProps,
|
||||
const FMathematicalMaterialProperties& MaterialProps,
|
||||
float VelocityMPS
|
||||
);
|
||||
|
||||
// Utility function to convert feet per second to meters per second
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float ConvertFPStoMPS(float FPS) { return FPS * 0.3048f; }
|
||||
|
||||
// Utility function to convert meters per second to feet per second
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float ConvertMPStoFPS(float MPS) { return MPS / 0.3048f; }
|
||||
|
||||
// Utility function to convert foot-pounds to joules
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float ConvertFtLbsToJoules(float FtLbs) { return FtLbs * 1.35582f; }
|
||||
|
||||
// Utility function to convert joules to foot-pounds
|
||||
UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics")
|
||||
static float ConvertJoulesToFtLbs(float Joules) { return Joules / 1.35582f; }
|
||||
|
||||
private:
|
||||
// Helper functions for complex calculations
|
||||
static float CalculateHardnessRatio(float BulletHardness, float MaterialHardness);
|
||||
static float CalculateVelocityFactor(float Velocity, float ThresholdVelocity);
|
||||
static float CalculateAngleFactor(float ImpactAngleDegrees);
|
||||
static float CalculateShapeFactorFromBulletType(EBulletType BulletType);
|
||||
static float CalculateMaterialFactor(EBulletMaterial BulletMaterial);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
||||
// Copyright 2018 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleInterface.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FEasyBallisticsModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class EasyBallisticsEditor : ModuleRules
|
||||
{
|
||||
public EasyBallisticsEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[] {
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"UnrealEd",
|
||||
"AssetTools",
|
||||
"EditorWidgets",
|
||||
"EasyBallistics"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"PropertyEditor",
|
||||
"WorkspaceMenuStructure",
|
||||
"DesktopPlatform",
|
||||
"ToolMenus"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBarrelComponentFactory.h"
|
||||
#include "EBBarrel.h"
|
||||
#include "AssetToolsModule.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "EBBarrelComponentFactory"
|
||||
|
||||
UClass* FEBBarrelComponentFactory::GetSupportedClass() const
|
||||
{
|
||||
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))
|
||||
{
|
||||
// Components don't typically have assets assigned to them directly
|
||||
// This would be used if we had barrel configuration assets
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UObject* FEBBarrelComponentAssetBroker::GetAssetFromComponent(UActorComponent* InComponent)
|
||||
{
|
||||
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,43 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBulletActorFactory.h"
|
||||
#include "EBBullet.h"
|
||||
#include "AssetToolsModule.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "EBBulletActorFactory"
|
||||
|
||||
UEBBulletActorFactory::UEBBulletActorFactory()
|
||||
{
|
||||
DisplayName = LOCTEXT("EBBulletActorDisplayName", "Bullet Actor");
|
||||
NewActorClass = AEBBullet::StaticClass();
|
||||
bUseSurfaceOrientation = true;
|
||||
}
|
||||
|
||||
bool UEBBulletActorFactory::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
AActor* UEBBulletActorFactory::SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams)
|
||||
{
|
||||
return InLevel->OwningWorld->SpawnActor<AEBBullet>(NewActorClass, InTransform, InSpawnParams);
|
||||
}
|
||||
|
||||
UClass* FEBBulletActorFactory::GetSupportedClass() const
|
||||
{
|
||||
return AEBBullet::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FEBBulletActorFactory::GetCategories()
|
||||
{
|
||||
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics"));
|
||||
}
|
||||
|
||||
FText FEBBulletActorFactory::GetAssetDescription(const FAssetData& AssetData) const
|
||||
{
|
||||
return LOCTEXT("EBBulletActorDescription", "A ballistic projectile actor with realistic physics simulation including drag, atmospheric effects, penetration, and ricochets.");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBBulletPropertiesFactory.h"
|
||||
#include "EBBulletProperties.h"
|
||||
#include "AssetToolsModule.h"
|
||||
#include "EasyBallisticsEditor.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "EBBulletPropertiesFactory"
|
||||
|
||||
// Bullet Properties Asset Factory
|
||||
UEBBulletPropertiesAssetFactory::UEBBulletPropertiesAssetFactory(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
SupportedClass = UEBBulletPropertiesAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UEBBulletPropertiesAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UEBBulletPropertiesAsset>(InParent, Class, Name, Flags);
|
||||
}
|
||||
|
||||
UClass* FEBBulletPropertiesAssetFactory::GetSupportedClass() const
|
||||
{
|
||||
return UEBBulletPropertiesAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FEBBulletPropertiesAssetFactory::GetCategories()
|
||||
{
|
||||
return FEasyBallisticsEditorModule::GetBallisticsAssetCategory();
|
||||
}
|
||||
|
||||
FText FEBBulletPropertiesAssetFactory::GetAssetDescription(const FAssetData& AssetData) const
|
||||
{
|
||||
return LOCTEXT("EBBulletPropertiesAssetDescription", "Defines mathematical properties of bullets including weight, dimensions, ballistic coefficients, and penetration characteristics.");
|
||||
}
|
||||
|
||||
// Material Properties Asset Factory
|
||||
UEBMaterialPropertiesAssetFactory::UEBMaterialPropertiesAssetFactory(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
SupportedClass = UEBMaterialPropertiesAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UEBMaterialPropertiesAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UEBMaterialPropertiesAsset>(InParent, Class, Name, Flags);
|
||||
}
|
||||
|
||||
UClass* FEBMaterialPropertiesAssetFactory::GetSupportedClass() const
|
||||
{
|
||||
return UEBMaterialPropertiesAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FEBMaterialPropertiesAssetFactory::GetCategories()
|
||||
{
|
||||
return FEasyBallisticsEditorModule::GetBallisticsAssetCategory();
|
||||
}
|
||||
|
||||
FText FEBMaterialPropertiesAssetFactory::GetAssetDescription(const FAssetData& AssetData) const
|
||||
{
|
||||
return LOCTEXT("EBMaterialPropertiesAssetDescription", "Defines mathematical properties of materials including density, hardness, tensile strength, and ballistic resistance characteristics.");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBMaterialResponseMapFactory.h"
|
||||
#include "EBMaterialResponseMap.h"
|
||||
#include "AssetToolsModule.h"
|
||||
#include "EasyBallisticsEditor.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "EBMaterialResponseMapFactory"
|
||||
|
||||
UEBMaterialResponseMapFactory::UEBMaterialResponseMapFactory(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
SupportedClass = UEBMaterialResponseMap::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UEBMaterialResponseMapFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UEBMaterialResponseMap>(InParent, Class, Name, Flags);
|
||||
}
|
||||
|
||||
UClass* FEBMaterialResponseMapFactory::GetSupportedClass() const
|
||||
{
|
||||
return UEBMaterialResponseMap::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FEBMaterialResponseMapFactory::GetCategories()
|
||||
{
|
||||
return FEasyBallisticsEditorModule::GetBallisticsAssetCategory();
|
||||
}
|
||||
|
||||
FText FEBMaterialResponseMapFactory::GetAssetDescription(const FAssetData& AssetData) const
|
||||
{
|
||||
return LOCTEXT("EBMaterialResponseMapDescription", "Defines how projectiles interact with different physical materials including penetration and ricochet properties.");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBMathematicalBallisticsFactory.h"
|
||||
#include "EBMathematicalBallistics.h"
|
||||
#include "AssetToolsModule.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "EBMathematicalBallisticsFactory"
|
||||
|
||||
UClass* FEBMathematicalBallisticsFactory::GetSupportedClass() const
|
||||
{
|
||||
return UEBMathematicalBallistics::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FEBMathematicalBallisticsFactory::GetCategories()
|
||||
{
|
||||
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics"));
|
||||
}
|
||||
|
||||
FText FEBMathematicalBallisticsFactory::GetAssetDescription(const FAssetData& AssetData) const
|
||||
{
|
||||
return LOCTEXT("EBMathematicalBallisticsDescription", "Mathematical ballistics calculator providing penetration depth, residual velocity, and trajectory calculations using empirical formulas.");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBPhysicalMaterialCustomization.h"
|
||||
#include "PropertyEditorModule.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
#include "Widgets/Layout/SBox.h"
|
||||
#include "Widgets/Images/SImage.h"
|
||||
#include "EditorStyleSet.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "ContentBrowserModule.h"
|
||||
#include "IContentBrowserSingleton.h"
|
||||
#include "Framework/Notifications/NotificationManager.h"
|
||||
#include "Widgets/Notifications/SNotificationList.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "EBPhysicalMaterialCustomization"
|
||||
|
||||
TSharedRef<IDetailCustomization> FEBPhysicalMaterialCustomization::MakeInstance()
|
||||
{
|
||||
return MakeShareable(new FEBPhysicalMaterialCustomization);
|
||||
}
|
||||
|
||||
void FEBPhysicalMaterialCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
|
||||
{
|
||||
DetailBuilderPtr = &DetailBuilder;
|
||||
|
||||
TArray<TWeakObjectPtr<UObject>> Objects;
|
||||
DetailBuilder.GetObjectsBeingCustomized(Objects);
|
||||
|
||||
if (Objects.Num() == 1)
|
||||
{
|
||||
PhysicalMaterialPtr = Cast<UPhysicalMaterial>(Objects[0].Get());
|
||||
|
||||
if (PhysicalMaterialPtr.IsValid())
|
||||
{
|
||||
// Add Ballistics category
|
||||
IDetailCategoryBuilder& BallisticsCategory = DetailBuilder.EditCategory("Ballistics", LOCTEXT("BallisticsCategory", "Ballistics"), ECategoryPriority::Important);
|
||||
|
||||
UEBMaterialPropertiesAsset* CurrentProperties = GetBallisticProperties(PhysicalMaterialPtr.Get());
|
||||
|
||||
// Show current ballistic properties if assigned
|
||||
if (CurrentProperties)
|
||||
{
|
||||
BallisticsCategory.AddCustomRow(LOCTEXT("CurrentPropertiesLabel", "Ballistic Properties"))
|
||||
.NameContent()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("CurrentPropertiesName", "Ballistic Properties"))
|
||||
.Font(IDetailLayoutBuilder::GetDetailFont())
|
||||
]
|
||||
.ValueContent()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(CurrentProperties->GetName()))
|
||||
.Font(IDetailLayoutBuilder::GetDetailFont())
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(2.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("EditProperties", "Edit"))
|
||||
.OnClicked(this, &FEBPhysicalMaterialCustomization::OnEditBallisticProperties)
|
||||
.ToolTipText(LOCTEXT("EditPropertiesTooltip", "Edit the ballistic properties asset"))
|
||||
]
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show assignment options
|
||||
BallisticsCategory.AddCustomRow(LOCTEXT("AssignPropertiesLabel", "Assign Ballistic Properties"))
|
||||
.WholeRowContent()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(2.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("CreateNew", "Create New"))
|
||||
.OnClicked(this, &FEBPhysicalMaterialCustomization::OnCreateBallisticProperties)
|
||||
.ToolTipText(LOCTEXT("CreateNewTooltip", "Create a new ballistic properties asset for this material"))
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(2.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("AssignExisting", "Assign Existing"))
|
||||
.OnClicked(this, &FEBPhysicalMaterialCustomization::OnAssignBallisticProperties)
|
||||
.ToolTipText(LOCTEXT("AssignExistingTooltip", "Assign an existing ballistic properties asset to this material"))
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FReply FEBPhysicalMaterialCustomization::OnCreateBallisticProperties()
|
||||
{
|
||||
if (PhysicalMaterialPtr.IsValid())
|
||||
{
|
||||
// Create new Material Properties Asset
|
||||
FString PackageName = PhysicalMaterialPtr->GetPackage()->GetName() + TEXT("_BallisticProps");
|
||||
FString AssetName = PhysicalMaterialPtr->GetName() + TEXT("_BallisticProps");
|
||||
|
||||
UPackage* Package = CreatePackage(*PackageName);
|
||||
UEBMaterialPropertiesAsset* NewAsset = NewObject<UEBMaterialPropertiesAsset>(Package, *AssetName, RF_Public | RF_Standalone);
|
||||
|
||||
// Set some sensible defaults based on material name
|
||||
FString MaterialName = PhysicalMaterialPtr->GetName().ToLower();
|
||||
if (MaterialName.Contains("steel") || MaterialName.Contains("metal"))
|
||||
{
|
||||
NewAsset->MaterialProperties.DensityGPerCm3 = 7.85f;
|
||||
NewAsset->MaterialProperties.MaterialHardness = 200.0f;
|
||||
NewAsset->MaterialName = "Steel";
|
||||
}
|
||||
else if (MaterialName.Contains("wood"))
|
||||
{
|
||||
NewAsset->MaterialProperties.DensityGPerCm3 = 0.6f;
|
||||
NewAsset->MaterialProperties.MaterialHardness = 30.0f;
|
||||
NewAsset->MaterialName = "Wood";
|
||||
}
|
||||
else if (MaterialName.Contains("concrete"))
|
||||
{
|
||||
NewAsset->MaterialProperties.DensityGPerCm3 = 2.4f;
|
||||
NewAsset->MaterialProperties.MaterialHardness = 100.0f;
|
||||
NewAsset->MaterialName = "Concrete";
|
||||
}
|
||||
|
||||
// Mark package dirty and register with asset registry
|
||||
Package->MarkPackageDirty();
|
||||
FAssetRegistryModule::AssetCreated(NewAsset);
|
||||
|
||||
// Associate with physical material
|
||||
SetBallisticProperties(PhysicalMaterialPtr.Get(), NewAsset);
|
||||
|
||||
// Refresh the details panel
|
||||
if (DetailBuilderPtr)
|
||||
{
|
||||
DetailBuilderPtr->ForceRefreshDetails();
|
||||
}
|
||||
|
||||
// Show success notification
|
||||
FNotificationInfo Info(LOCTEXT("CreatedBallisticProps", "Created ballistic properties asset"));
|
||||
Info.ExpireDuration = 3.0f;
|
||||
FSlateNotificationManager::Get().AddNotification(Info);
|
||||
}
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply FEBPhysicalMaterialCustomization::OnAssignBallisticProperties()
|
||||
{
|
||||
// Open content browser to select existing Material Properties Asset
|
||||
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
||||
|
||||
FAssetPickerConfig AssetPickerConfig;
|
||||
AssetPickerConfig.Filter.ClassPaths.Add(UEBMaterialPropertiesAsset::StaticClass()->GetClassPathName());
|
||||
AssetPickerConfig.bAllowNullSelection = false;
|
||||
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateLambda([this](const FAssetData& AssetData)
|
||||
{
|
||||
if (UEBMaterialPropertiesAsset* SelectedAsset = Cast<UEBMaterialPropertiesAsset>(AssetData.GetAsset()))
|
||||
{
|
||||
SetBallisticProperties(PhysicalMaterialPtr.Get(), SelectedAsset);
|
||||
|
||||
if (DetailBuilderPtr)
|
||||
{
|
||||
DetailBuilderPtr->ForceRefreshDetails();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig);
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply FEBPhysicalMaterialCustomization::OnEditBallisticProperties()
|
||||
{
|
||||
if (UEBMaterialPropertiesAsset* Properties = GetBallisticProperties(PhysicalMaterialPtr.Get()))
|
||||
{
|
||||
// Open the asset for editing
|
||||
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Properties);
|
||||
}
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
UEBMaterialPropertiesAsset* FEBPhysicalMaterialCustomization::GetBallisticProperties(UPhysicalMaterial* PhysMat) const
|
||||
{
|
||||
if (!PhysMat) return nullptr;
|
||||
|
||||
// For now, use a simple naming convention or metadata
|
||||
// In a production system, you might add a UPROPERTY to UPhysicalMaterial
|
||||
FString AssetName = PhysMat->GetName() + TEXT("_BallisticProps");
|
||||
FString PackageName = PhysMat->GetPackage()->GetName() + TEXT("_BallisticProps");
|
||||
|
||||
return LoadObject<UEBMaterialPropertiesAsset>(nullptr, *PackageName, nullptr, LOAD_NoWarn | LOAD_Quiet);
|
||||
}
|
||||
|
||||
void FEBPhysicalMaterialCustomization::SetBallisticProperties(UPhysicalMaterial* PhysMat, UEBMaterialPropertiesAsset* Properties)
|
||||
{
|
||||
// In a production system, you would store this reference properly
|
||||
// For now, we rely on naming conventions and the Material Response Map system
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EasyBallisticsEditor.h"
|
||||
#include "AssetToolsModule.h"
|
||||
#include "PropertyEditorModule.h"
|
||||
#include "EBMaterialResponseMapFactory.h"
|
||||
#include "EBBulletPropertiesFactory.h"
|
||||
#include "EBBulletActorFactory.h"
|
||||
#include "EBBarrelComponentFactory.h"
|
||||
#include "EBMathematicalBallisticsFactory.h"
|
||||
#include "EBPhysicalMaterialCustomization.h"
|
||||
#include "PhysicalMaterials/PhysicalMaterial.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FEasyBallisticsEditorModule"
|
||||
|
||||
EAssetTypeCategories::Type FEasyBallisticsEditorModule::BallisticsAssetCategory;
|
||||
|
||||
void FEasyBallisticsEditorModule::StartupModule()
|
||||
{
|
||||
// Register asset factories
|
||||
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
|
||||
// Register the Ballistics category
|
||||
BallisticsAssetCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics"));
|
||||
|
||||
// Store references to our asset type actions for cleanup
|
||||
RegisteredAssetTypeActions.Empty();
|
||||
|
||||
// Register Material Response Map factory
|
||||
{
|
||||
TSharedRef<IAssetTypeActions> MaterialResponseMapActions = MakeShareable(new FEBMaterialResponseMapFactory());
|
||||
AssetTools.RegisterAssetTypeActions(MaterialResponseMapActions);
|
||||
RegisteredAssetTypeActions.Add(MaterialResponseMapActions);
|
||||
}
|
||||
|
||||
// Register Bullet Properties Asset factory
|
||||
{
|
||||
TSharedRef<IAssetTypeActions> BulletPropertiesActions = MakeShareable(new FEBBulletPropertiesAssetFactory());
|
||||
AssetTools.RegisterAssetTypeActions(BulletPropertiesActions);
|
||||
RegisteredAssetTypeActions.Add(BulletPropertiesActions);
|
||||
}
|
||||
|
||||
// Register Physical Material customization
|
||||
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
PropertyModule.RegisterCustomClassLayout(
|
||||
UPhysicalMaterial::StaticClass()->GetFName(),
|
||||
FOnGetDetailCustomizationInstance::CreateStatic(&FEBPhysicalMaterialCustomization::MakeInstance)
|
||||
);
|
||||
}
|
||||
|
||||
void FEasyBallisticsEditorModule::ShutdownModule()
|
||||
{
|
||||
// Unregister Physical Material customization
|
||||
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
|
||||
{
|
||||
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
PropertyModule.UnregisterCustomClassLayout(UPhysicalMaterial::StaticClass()->GetFName());
|
||||
}
|
||||
|
||||
// Unregister asset type actions
|
||||
if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
|
||||
{
|
||||
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
|
||||
for (auto& AssetTypeAction : RegisteredAssetTypeActions)
|
||||
{
|
||||
if (AssetTypeAction.IsValid())
|
||||
{
|
||||
AssetTools.UnregisterAssetTypeActions(AssetTypeAction.ToSharedRef());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RegisteredAssetTypeActions.Empty();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FEasyBallisticsEditorModule, EasyBallisticsEditor)
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
#include "ComponentAssetBroker.h"
|
||||
#include "EBBarrel.h"
|
||||
|
||||
class FEBBarrelComponentFactory : public FAssetTypeActions_Base
|
||||
{
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
#include "ActorFactories/ActorFactory.h"
|
||||
#include "EBBullet.h"
|
||||
#include "EBBulletActorFactory.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class EASYBALLISTICSEDITOR_API UEBBulletActorFactory : public UActorFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UEBBulletActorFactory();
|
||||
|
||||
virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override;
|
||||
virtual AActor* SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams) override;
|
||||
};
|
||||
|
||||
class FEBBulletActorFactory : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBBulletActor", "Bullet Actor"); }
|
||||
virtual FColor GetTypeColor() const override { return FColor(255, 100, 100); }
|
||||
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; }
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
#include "Factories/Factory.h"
|
||||
#include "EBBulletProperties.h"
|
||||
#include "EBBulletPropertiesFactory.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class EASYBALLISTICSEDITOR_API UEBBulletPropertiesAssetFactory : 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", "BulletPropertiesText", "Bullet Properties"); }
|
||||
virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "BulletPropertiesTooltip", "Creates a new Bullet Properties asset for configuring ballistic projectile characteristics"); }
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class EASYBALLISTICSEDITOR_API UEBMaterialPropertiesAssetFactory : 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", "MaterialPropertiesText", "Material Properties"); }
|
||||
virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "MaterialPropertiesTooltip", "Creates a new Material Properties asset for configuring material ballistic resistance"); }
|
||||
};
|
||||
|
||||
class FEBBulletPropertiesAssetFactory : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBBulletPropertiesAsset", "Bullet Properties"); }
|
||||
virtual FColor GetTypeColor() const override { return FColor(255, 200, 100); }
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
virtual FText GetAssetDescription(const FAssetData& AssetData) const override;
|
||||
};
|
||||
|
||||
class FEBMaterialPropertiesAssetFactory : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBMaterialPropertiesAsset", "Material Properties"); }
|
||||
virtual FColor GetTypeColor() const override { return FColor(150, 255, 150); }
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
virtual FText GetAssetDescription(const FAssetData& AssetData) const override;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
#include "Factories/Factory.h"
|
||||
#include "EBMaterialResponseMap.h"
|
||||
#include "EBMaterialResponseMapFactory.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class EASYBALLISTICSEDITOR_API UEBMaterialResponseMapFactory : 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", "MaterialResponseMapText", "Material Response Map"); }
|
||||
virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "MaterialResponseMapTooltip", "Creates a new Material Response Map for configuring ballistic material interactions"); }
|
||||
};
|
||||
|
||||
class FEBMaterialResponseMapFactory : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBMaterialResponseMap", "Material Response Map"); }
|
||||
virtual FColor GetTypeColor() const override { return FColor(255, 127, 64); }
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
virtual FText GetAssetDescription(const FAssetData& AssetData) const override;
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
#include "EBMathematicalBallistics.h"
|
||||
|
||||
class FEBMathematicalBallisticsFactory : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBMathematicalBallistics", "Mathematical Ballistics"); }
|
||||
virtual FColor GetTypeColor() const override { return FColor(150, 100, 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; }
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "IDetailCustomization.h"
|
||||
#include "DetailLayoutBuilder.h"
|
||||
#include "DetailCategoryBuilder.h"
|
||||
#include "DetailWidgetRow.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "PhysicalMaterials/PhysicalMaterial.h"
|
||||
#include "EBBulletProperties.h"
|
||||
|
||||
class FEBPhysicalMaterialCustomization : public IDetailCustomization
|
||||
{
|
||||
public:
|
||||
static TSharedRef<IDetailCustomization> MakeInstance();
|
||||
|
||||
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
|
||||
|
||||
private:
|
||||
FReply OnAssignBallisticProperties();
|
||||
FReply OnCreateBallisticProperties();
|
||||
FReply OnEditBallisticProperties();
|
||||
|
||||
TWeakObjectPtr<UPhysicalMaterial> PhysicalMaterialPtr;
|
||||
IDetailLayoutBuilder* DetailBuilderPtr;
|
||||
|
||||
UEBMaterialPropertiesAsset* GetBallisticProperties(UPhysicalMaterial* PhysMat) const;
|
||||
void SetBallisticProperties(UPhysicalMaterial* PhysMat, UEBMaterialPropertiesAsset* Properties);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
#include "AssetToolsModule.h"
|
||||
|
||||
class FEasyBallisticsEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
static EAssetTypeCategories::Type GetBallisticsAssetCategory() { return BallisticsAssetCategory; }
|
||||
|
||||
private:
|
||||
TArray<TSharedPtr<IAssetTypeActions>> RegisteredAssetTypeActions;
|
||||
static EAssetTypeCategories::Type BallisticsAssetCategory;
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production build
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
Thumbs.db
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
# EasyBallistics Documentation
|
||||
|
||||
This directory contains the complete documentation for EasyBallistics, built with [Docusaurus](https://docusaurus.io/).
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Node.js** 18.0 or higher
|
||||
- **npm** or **yarn**
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
npm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
Start the development server:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
### Build
|
||||
|
||||
Generate static content:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
## 📚 Documentation Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── docs/ # Documentation content
|
||||
│ ├── intro.md # Homepage/Introduction
|
||||
│ ├── getting-started/ # Installation and quick start
|
||||
│ ├── core-concepts/ # Fundamental concepts
|
||||
│ ├── assets/ # Asset creation guides
|
||||
│ ├── components/ # Component documentation
|
||||
│ ├── mathematical/ # Mathematical ballistics
|
||||
│ ├── networking/ # Multiplayer features
|
||||
│ ├── performance/ # Optimization guides
|
||||
│ ├── tutorials/ # Step-by-step tutorials
|
||||
│ ├── api/ # API reference
|
||||
│ ├── migration/ # Migration guides
|
||||
│ ├── troubleshooting.md # Common issues
|
||||
│ └── changelog.md # Version history
|
||||
├── src/ # Custom React components
|
||||
│ └── css/ # Custom styling
|
||||
├── static/ # Static assets
|
||||
│ └── img/ # Images and screenshots
|
||||
├── docusaurus.config.js # Site configuration
|
||||
├── sidebars.js # Navigation structure
|
||||
└── package.json # Dependencies
|
||||
```
|
||||
|
||||
## 🎨 Customization
|
||||
|
||||
### Styling
|
||||
|
||||
Custom CSS is located in `src/css/custom.css`. This includes:
|
||||
- Color scheme customization
|
||||
- Component-specific styles
|
||||
- Responsive design adjustments
|
||||
|
||||
### Configuration
|
||||
|
||||
Main configuration is in `docusaurus.config.js`:
|
||||
- Site metadata
|
||||
- Navigation structure
|
||||
- Theme configuration
|
||||
- Plugin settings
|
||||
|
||||
### Components
|
||||
|
||||
Custom React components for enhanced documentation:
|
||||
- Code examples with syntax highlighting
|
||||
- Interactive API documentation
|
||||
- Embedded demos and screenshots
|
||||
|
||||
## 📝 Content Guidelines
|
||||
|
||||
### Writing Style
|
||||
|
||||
- **Clear and Concise**: Use simple, direct language
|
||||
- **Code Examples**: Include practical, working examples
|
||||
- **Screenshots**: Add visual aids for UI-heavy sections
|
||||
- **Cross-References**: Link to related documentation
|
||||
|
||||
### File Naming
|
||||
|
||||
- Use kebab-case for file names: `getting-started.md`
|
||||
- Group related content in folders
|
||||
- Keep URLs readable and SEO-friendly
|
||||
|
||||
### Markdown Features
|
||||
|
||||
Docusaurus supports enhanced markdown:
|
||||
|
||||
```markdown
|
||||
:::info
|
||||
Information callouts for important notes
|
||||
:::
|
||||
|
||||
:::warning
|
||||
Warning callouts for potential issues
|
||||
:::
|
||||
|
||||
:::danger
|
||||
Danger callouts for critical warnings
|
||||
:::
|
||||
|
||||
```cpp title="Example.cpp"
|
||||
// Code blocks with titles and syntax highlighting
|
||||
void ExampleFunction()
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Start] --> B[Process]
|
||||
B --> C[End]
|
||||
```
|
||||
```
|
||||
|
||||
## 🚢 Deployment
|
||||
|
||||
### GitHub Pages
|
||||
|
||||
Deploy to GitHub Pages:
|
||||
|
||||
```bash
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
### Custom Hosting
|
||||
|
||||
Build and deploy to any static hosting service:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
# Upload the build/ directory to your hosting provider
|
||||
```
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
Example GitHub Actions workflow:
|
||||
|
||||
```yaml
|
||||
name: Deploy Documentation
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: ['docs/**']
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
- name: Install dependencies
|
||||
run: cd docs && npm install
|
||||
- name: Build documentation
|
||||
run: cd docs && npm run build
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs/build
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
### Documentation Contributions
|
||||
|
||||
1. **Fork the repository**
|
||||
2. **Create a documentation branch**: `git checkout -b docs/feature-name`
|
||||
3. **Make your changes** following the content guidelines
|
||||
4. **Test locally**: `npm start` to preview changes
|
||||
5. **Submit a pull request** with a clear description
|
||||
|
||||
### Content Types
|
||||
|
||||
We welcome contributions for:
|
||||
- **Tutorials**: Step-by-step guides for specific use cases
|
||||
- **Examples**: Code samples and implementation patterns
|
||||
- **Troubleshooting**: Solutions to common problems
|
||||
- **API Documentation**: Detailed function and class references
|
||||
- **Screenshots**: Visual aids for complex procedures
|
||||
|
||||
### Review Process
|
||||
|
||||
All documentation changes go through:
|
||||
1. **Technical Review**: Accuracy and completeness
|
||||
2. **Editorial Review**: Grammar, style, and clarity
|
||||
3. **Testing**: Verify examples and instructions work
|
||||
4. **Deployment**: Merge and publish updates
|
||||
|
||||
## 🛠️ Development Tools
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# Start development server
|
||||
npm start
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Serve production build locally
|
||||
npm run serve
|
||||
|
||||
# Clear build cache
|
||||
npm run clear
|
||||
|
||||
# Generate heading IDs
|
||||
npm run write-heading-ids
|
||||
|
||||
# Extract translatable strings
|
||||
npm run write-translations
|
||||
```
|
||||
|
||||
### VS Code Extensions
|
||||
|
||||
Recommended extensions for documentation development:
|
||||
- **Markdown All in One**: Enhanced markdown editing
|
||||
- **Code Spell Checker**: Catch typos and spelling errors
|
||||
- **Prettier**: Consistent code formatting
|
||||
- **Auto Rename Tag**: HTML/JSX tag editing
|
||||
|
||||
## 📊 Analytics and Monitoring
|
||||
|
||||
### Google Analytics
|
||||
|
||||
Analytics are configured in `docusaurus.config.js`:
|
||||
|
||||
```javascript
|
||||
gtag: {
|
||||
trackingID: 'G-XXXXXXXXXX',
|
||||
anonymizeIP: true,
|
||||
}
|
||||
```
|
||||
|
||||
### Search
|
||||
|
||||
Built-in search is provided by Algolia DocSearch:
|
||||
|
||||
```javascript
|
||||
algolia: {
|
||||
apiKey: 'your-api-key',
|
||||
indexName: 'easyballistics',
|
||||
contextualSearch: true,
|
||||
}
|
||||
```
|
||||
|
||||
## 🐛 Issues and Support
|
||||
|
||||
### Reporting Documentation Issues
|
||||
|
||||
When reporting documentation issues:
|
||||
1. **Specify the page**: Include the URL or file path
|
||||
2. **Describe the problem**: What's unclear or incorrect
|
||||
3. **Suggest improvements**: How could it be better
|
||||
4. **Provide context**: Your use case or scenario
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Discord**: [Real-time community support](https://discord.gg/easyballistics)
|
||||
- **GitHub Issues**: [Report documentation bugs](https://github.com/your-org/easyballistics/issues)
|
||||
- **Email**: docs@easyballistics.com
|
||||
|
||||
## 📄 License
|
||||
|
||||
Documentation is licensed under [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/).
|
||||
|
||||
Code examples within the documentation follow the same license as the EasyBallistics plugin.
|
||||
@@ -0,0 +1,365 @@
|
||||
# API Reference Overview
|
||||
|
||||
Complete reference for all EasyBallistics classes, functions, and events.
|
||||
|
||||
## Core Classes
|
||||
|
||||
### Actors
|
||||
|
||||
| Class | Description | Module |
|
||||
|-------|-------------|---------|
|
||||
| [`AEBBullet`](bullet-actor) | Main projectile actor with physics simulation | Runtime |
|
||||
|
||||
### Components
|
||||
|
||||
| Class | Description | Module |
|
||||
|-------|-------------|---------|
|
||||
| [`UEBBarrel`](barrel-component) | Weapon barrel with firing mechanics | Runtime |
|
||||
| [`UEBBallisticImpactComponent`](impact-component) | Impact handling and event system | Runtime |
|
||||
|
||||
### Assets
|
||||
|
||||
| Class | Description | Module |
|
||||
|-------|-------------|---------|
|
||||
| `UEBBulletPropertiesAsset` | Bullet characteristic definitions | Runtime |
|
||||
| `UEBMaterialPropertiesAsset` | Material ballistic properties | Runtime |
|
||||
| `UEBMaterialResponseMap` | Material interaction rules | Runtime |
|
||||
|
||||
### Utility Classes
|
||||
|
||||
| Class | Description | Module |
|
||||
|-------|-------------|---------|
|
||||
| [`UEBMathematicalBallistics`](mathematical-ballistics) | Static calculation functions | Runtime |
|
||||
|
||||
## Enumerations
|
||||
|
||||
### EFireMode
|
||||
Weapon firing modes supported by UEBBarrel.
|
||||
|
||||
```cpp
|
||||
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")
|
||||
};
|
||||
```
|
||||
|
||||
### EEBAtmosphereType
|
||||
Atmospheric simulation models for ballistic calculations.
|
||||
|
||||
```cpp
|
||||
UENUM(BlueprintType)
|
||||
enum class EEBAtmosphereType : uint8
|
||||
{
|
||||
AT_Constant UMETA(DisplayName = "Constant"),
|
||||
AT_Curve UMETA(DisplayName = "Density Curve"),
|
||||
AT_Earth UMETA(DisplayName = "Earth/IGL")
|
||||
};
|
||||
```
|
||||
|
||||
### EPenTraceType
|
||||
Penetration trace algorithms for material thickness calculation.
|
||||
|
||||
```cpp
|
||||
UENUM(BlueprintType)
|
||||
enum class EPenTraceType : uint8
|
||||
{
|
||||
PT_BackTrace UMETA(DisplayName = "Back Trace"),
|
||||
PT_ByComponent UMETA(DisplayName = "By Component"),
|
||||
PT_TwoSidedGeometry UMETA(DisplayName = "Double Sided Geometry")
|
||||
};
|
||||
```
|
||||
|
||||
## Data Structures
|
||||
|
||||
### FMathematicalBulletProperties
|
||||
Defines physical and ballistic properties of projectiles.
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMathematicalBulletProperties
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
// Basic Properties
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties")
|
||||
float GrainWeight = 55.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties")
|
||||
float DiameterInches = 0.224f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties")
|
||||
float LengthInches = 0.825f;
|
||||
|
||||
// Ballistic Properties
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
|
||||
float BallisticCoefficientG1 = 0.151f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
|
||||
float BallisticCoefficientG7 = 0.076f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics")
|
||||
bool UseG7Model = false;
|
||||
|
||||
// Utility functions
|
||||
float GetSectionalDensity() const;
|
||||
float GetMassKg() const;
|
||||
float GetDiameterCm() const;
|
||||
float GetCrossSectionCm2() const;
|
||||
};
|
||||
```
|
||||
|
||||
### FMathematicalMaterialProperties
|
||||
Defines physical properties of materials for ballistic calculations.
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMathematicalMaterialProperties
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
// Material Properties
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties")
|
||||
float DensityGPerCm3 = 7.85f; // Steel default
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties")
|
||||
float MaterialHardness = 200.0f; // Brinell Hardness
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties")
|
||||
float TensileStrengthMPa = 400.0f;
|
||||
|
||||
// Ballistic Properties
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties")
|
||||
float BallisticLimitVelocity = 2000.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties")
|
||||
float PerforationCoefficient = 1.0f;
|
||||
};
|
||||
```
|
||||
|
||||
### FEBMaterialResponseMapEntry
|
||||
Defines how bullets interact with specific materials.
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct FEBMaterialResponseMapEntry
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
// Penetration Properties
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties")
|
||||
bool NeverPenetrate = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties")
|
||||
float PenetrationDepthMultiplier = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties")
|
||||
float PenetrationNormalization = 0.0f;
|
||||
|
||||
// Ricochet Properties
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties")
|
||||
bool NeverRicochet = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties")
|
||||
float RicochetProbabilityMultiplier = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties")
|
||||
float RicochetRestitution = 0.5f;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Artistic Properties")
|
||||
float RicochetFriction = 0.5f;
|
||||
|
||||
// Mathematical Properties
|
||||
UPROPERTY(EditAnywhere, Category = "Mathematical Properties")
|
||||
bool UseMathematicalProperties = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "Mathematical Properties")
|
||||
FMathematicalMaterialProperties MathematicalProperties;
|
||||
};
|
||||
```
|
||||
|
||||
## Events and Delegates
|
||||
|
||||
### Ballistic Impact Events
|
||||
|
||||
```cpp
|
||||
// Impact event - fired when bullets hit surfaces
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FOnBallisticImpact,
|
||||
FVector, ImpactLocation,
|
||||
FVector, ImpactNormal,
|
||||
UPhysicalMaterial*, HitMaterial,
|
||||
float, PenetrationDepth,
|
||||
bool, bDidPenetrate);
|
||||
|
||||
// Ricochet event - fired when bullets ricochet off surfaces
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnBallisticRicochet,
|
||||
FVector, RicochetLocation,
|
||||
FVector, RicochetDirection,
|
||||
UPhysicalMaterial*, HitMaterial,
|
||||
float, EnergyRetained);
|
||||
```
|
||||
|
||||
### Barrel Events
|
||||
|
||||
```cpp
|
||||
// Firing events
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events")
|
||||
void OnFireSingle(FVector MuzzleLocation, FVector MuzzleDirection);
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events")
|
||||
void OnFireBurst(int32 ShotsFired, FVector MuzzleLocation, FVector MuzzleDirection);
|
||||
|
||||
// Ammo events
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events")
|
||||
void OnAmmoChanged(int32 CurrentAmmo, int32 MaxAmmo);
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events")
|
||||
void OnReloadStarted(float ReloadTime);
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events")
|
||||
void OnReloadCompleted();
|
||||
```
|
||||
|
||||
## Common Usage Patterns
|
||||
|
||||
### Basic Bullet Spawning
|
||||
|
||||
```cpp
|
||||
// Spawn a bullet with specific properties
|
||||
AEBBullet* Bullet = GetWorld()->SpawnActor<AEBBullet>();
|
||||
Bullet->BulletPropertiesAsset = MyBulletProperties;
|
||||
Bullet->MaterialResponseMap = MyMaterialMap;
|
||||
Bullet->UseNewImpactSystem = true;
|
||||
Bullet->Velocity = MuzzleDirection * MuzzleVelocity;
|
||||
```
|
||||
|
||||
### Impact Event Handling
|
||||
|
||||
```cpp
|
||||
// Bind to impact events in C++
|
||||
void AMyActor::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
if (BallisticImpactComponent)
|
||||
{
|
||||
BallisticImpactComponent->OnBallisticImpact.AddDynamic(
|
||||
this, &AMyActor::HandleBallisticImpact);
|
||||
BallisticImpactComponent->OnBallisticRicochet.AddDynamic(
|
||||
this, &AMyActor::HandleBallisticRicochet);
|
||||
}
|
||||
}
|
||||
|
||||
void AMyActor::HandleBallisticImpact(FVector ImpactLocation, FVector ImpactNormal,
|
||||
UPhysicalMaterial* HitMaterial, float PenetrationDepth, bool bDidPenetrate)
|
||||
{
|
||||
// Custom impact response
|
||||
if (bDidPenetrate)
|
||||
{
|
||||
SpawnPenetrationEffect(ImpactLocation, ImpactNormal);
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnImpactEffect(ImpactLocation, ImpactNormal);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mathematical Calculations
|
||||
|
||||
```cpp
|
||||
// Calculate penetration depth using mathematical model
|
||||
float PenetrationDepth = UEBMathematicalBallistics::CalculatePenetrationDepth(
|
||||
BulletProperties,
|
||||
MaterialProperties,
|
||||
VelocityMPS,
|
||||
ImpactAngleDegrees
|
||||
);
|
||||
|
||||
// Calculate ricochet probability
|
||||
float RicochetProbability = UEBMathematicalBallistics::CalculateRicochetProbability(
|
||||
BulletProperties,
|
||||
MaterialProperties,
|
||||
VelocityMPS,
|
||||
ImpactAngleDegrees
|
||||
);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Error Codes
|
||||
|
||||
| Error | Description | Solution |
|
||||
|-------|-------------|----------|
|
||||
| `NullBulletProperties` | Bullet spawned without properties | Assign UEBBulletPropertiesAsset |
|
||||
| `InvalidMaterialMap` | Material not found in response map | Add material to UEBMaterialResponseMap |
|
||||
| `MissingPhysicalMaterial` | Hit result has no physical material | Assign physical material to surface |
|
||||
| `NetworkDesync` | Client/server ballistic mismatch | Check network replication settings |
|
||||
|
||||
### Debugging Utilities
|
||||
|
||||
```cpp
|
||||
// Enable debug visualization
|
||||
Bullet->DebugEnabled = true;
|
||||
Bullet->DebugTrailTime = 2.0f;
|
||||
Bullet->DebugTrailColorFast = FLinearColor::Green;
|
||||
Bullet->DebugTrailColorSlow = FLinearColor::Red;
|
||||
|
||||
// Performance monitoring
|
||||
UE_LOG(LogEasyBallistics, Warning, TEXT("Bullet %s exceeded max traces: %d"),
|
||||
*Bullet->GetName(), Bullet->MaxTracesPerStep);
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
### Performance Scaling
|
||||
|
||||
| Platform | Recommended Settings | Notes |
|
||||
|----------|---------------------|-------|
|
||||
| High-end PC | Full mathematical mode | All features enabled |
|
||||
| Console | Hybrid mode | Mathematical for player weapons only |
|
||||
| Mobile | Artistic mode | Simplified calculations |
|
||||
| VR | Fixed timestep | Consistent simulation for comfort |
|
||||
|
||||
### Memory Management
|
||||
|
||||
```cpp
|
||||
// Configure pooling for different platforms
|
||||
#if PLATFORM_MOBILE
|
||||
Bullet->MaxPoolSize = 20; // Limited memory
|
||||
Bullet->MaxTracesPerStep = 2;
|
||||
#else
|
||||
Bullet->MaxPoolSize = 100; // More memory available
|
||||
Bullet->MaxTracesPerStep = 8;
|
||||
#endif
|
||||
```
|
||||
|
||||
## Migration Notes
|
||||
|
||||
### From Legacy System
|
||||
|
||||
When migrating from the old impact system:
|
||||
|
||||
1. Set `UseNewImpactSystem = true` on bullets
|
||||
2. Replace material property assignments with Physical Material integration
|
||||
3. Update impact event bindings to use new delegates
|
||||
4. Test penetration/ricochet behavior and adjust response maps
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `MaterialResponseMap` entries now use Physical Materials as keys
|
||||
- Impact events have different parameter signatures
|
||||
- Some artistic properties moved to new component system
|
||||
|
||||
## See Also
|
||||
|
||||
- [Component API Reference](bullet-actor) - Detailed component documentation
|
||||
- [Events Reference](events) - Complete event system documentation
|
||||
- [Mathematical Formulas](../mathematical/penetration-calculations) - Physics calculation details
|
||||
- [Migration Guide](../migration/new-impact-system) - Upgrading from legacy system
|
||||
@@ -0,0 +1,201 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to EasyBallistics will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.83.0] - 2024-12-02
|
||||
|
||||
### 🎉 Major Features Added
|
||||
|
||||
#### New Ballistic Impact System
|
||||
- **UEBBallisticImpactComponent**: Event-driven impact handling system
|
||||
- **Blueprint Events**: `OnBallisticImpact` and `OnBallisticRicochet` with detailed parameters
|
||||
- **Opt-in Migration**: `UseNewImpactSystem` flag for gradual adoption
|
||||
- **Mathematical Integration**: Full support for realistic penetration calculations
|
||||
|
||||
#### Physical Material Integration
|
||||
- **Material Editor Integration**: Ballistic properties directly in Physical Material editor
|
||||
- **Smart Asset Creation**: Auto-configured material properties based on material names
|
||||
- **Seamless Workflow**: Create/assign/edit ballistic properties without leaving the material editor
|
||||
|
||||
#### Enhanced Asset Creation System
|
||||
- **Ballistics Category**: Dedicated section in Content Browser asset creation menu
|
||||
- **Asset Factories**: Streamlined creation of Bullet Properties and Material Response Maps
|
||||
- **Editor Integration**: Custom property panels and specialized editors
|
||||
|
||||
### 🔧 Improvements
|
||||
|
||||
#### Performance Enhancements
|
||||
- **Optimized Pooling**: Improved object pooling system for high-rate fire scenarios
|
||||
- **LOD System**: Distance-based simulation quality scaling
|
||||
- **Memory Management**: Reduced memory allocations during bullet simulation
|
||||
|
||||
#### Network Improvements
|
||||
- **Better Prediction**: Enhanced client-side prediction for responsive gameplay
|
||||
- **Bandwidth Optimization**: Reduced network traffic for bullet replication
|
||||
- **Lag Compensation**: Improved hit detection with network latency
|
||||
|
||||
#### Developer Experience
|
||||
- **Enhanced Debugging**: Better debug visualization and profiling tools
|
||||
- **Error Handling**: Improved error messages and validation
|
||||
- **Documentation**: Comprehensive API documentation and tutorials
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
#### Core Systems
|
||||
- Fixed issue where bullets could pass through thin objects
|
||||
- Resolved memory leak in object pooling system
|
||||
- Corrected atmospheric calculations at extreme altitudes
|
||||
- Fixed ricochet angle calculations for steep impact angles
|
||||
|
||||
#### Network Issues
|
||||
- Fixed desync between client and server bullet positions
|
||||
- Resolved prediction errors causing visual "jumping"
|
||||
- Corrected replication issues with high-rate fire weapons
|
||||
- Fixed authority validation for impact events
|
||||
|
||||
#### Editor Issues
|
||||
- Fixed asset reference validation in property editor
|
||||
- Resolved compilation errors on certain platform configurations
|
||||
- Fixed crash when deleting Material Response Map entries
|
||||
- Corrected property categorization in component details panels
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
#### API Changes
|
||||
- **Material System**: Physical Materials now serve as primary material identifiers
|
||||
- **Event Signatures**: Impact events now include additional context parameters
|
||||
- **Component Architecture**: Ballistic calculations moved to dedicated component
|
||||
|
||||
#### Asset Workflow
|
||||
- **Material Properties**: No longer appear in main asset creation menu
|
||||
- **Physical Material Editor**: Now includes ballistic property management
|
||||
- **Response Maps**: Updated to use Physical Materials as keys instead of names
|
||||
|
||||
#### Performance Defaults
|
||||
- **Object Pooling**: Now enabled by default for all bullet classes
|
||||
- **Trace Complexity**: Automatic LOD based on distance and importance
|
||||
- **Network Settings**: Optimized default replication settings
|
||||
|
||||
### 🗑️ Deprecated
|
||||
|
||||
#### Legacy Systems
|
||||
- **Old Impact Calculation**: Marked for deprecation (still functional with `UseNewImpactSystem = false`)
|
||||
- **String-based Material Keys**: Material Response Maps should use Physical Material references
|
||||
- **Direct Material Property Assignment**: Use Physical Material integration instead
|
||||
|
||||
### 📋 Migration Notes
|
||||
|
||||
#### From 2.8x to 2.83
|
||||
1. **Enable New Impact System**: Set `UseNewImpactSystem = true` on bullet classes
|
||||
2. **Update Material Response Maps**: Replace string keys with Physical Material references
|
||||
3. **Migrate Impact Events**: Update event bindings to use new parameter signatures
|
||||
4. **Physical Material Setup**: Assign ballistic properties through Physical Material editor
|
||||
|
||||
#### Breaking Changes
|
||||
- Material Response Map keys changed from `FString` to `UPhysicalMaterial*`
|
||||
- Impact event parameter order and types modified
|
||||
- Some artistic ballistic properties moved to component-level settings
|
||||
|
||||
### 🛠️ Technical Details
|
||||
|
||||
#### New Classes
|
||||
- `UEBBallisticImpactComponent`: Impact handling and event broadcasting
|
||||
- `UEBBulletPropertiesAssetFactory`: Editor factory for bullet properties
|
||||
- `UEBMaterialResponseMapFactory`: Editor factory for material response maps
|
||||
- `FEBPhysicalMaterialCustomization`: Custom details panel for Physical Materials
|
||||
|
||||
#### Modified Classes
|
||||
- `AEBBullet`: Added new impact system integration and component support
|
||||
- `UEBMaterialResponseMap`: Updated to use Physical Material keys
|
||||
- `FEBMaterialResponseMapEntry`: Added mathematical properties support
|
||||
|
||||
#### Editor Enhancements
|
||||
- Custom asset type actions for improved Content Browser integration
|
||||
- Specialized property editors for ballistic assets
|
||||
- Enhanced debug visualization tools
|
||||
|
||||
### 📊 Performance Metrics
|
||||
|
||||
#### Benchmark Improvements
|
||||
- **Memory Usage**: 25% reduction in allocation overhead
|
||||
- **Frame Rate**: 15% improvement with 100+ active bullets
|
||||
- **Network Bandwidth**: 30% reduction in replication data
|
||||
- **Load Times**: 20% faster asset loading and initialization
|
||||
|
||||
#### Platform Support
|
||||
- **Windows**: Full feature support
|
||||
- **Mac**: Full feature support
|
||||
- **Linux**: Full feature support
|
||||
- **Android**: Optimized performance mode
|
||||
|
||||
### 🎯 Known Issues
|
||||
|
||||
#### Current Limitations
|
||||
- Mathematical ballistics may have precision issues at extreme ranges (>10km)
|
||||
- VR motion controller integration requires manual setup
|
||||
- Some console commands may not work in packaged builds
|
||||
|
||||
#### Workarounds
|
||||
- Use artistic mode for very long-range scenarios
|
||||
- Implement custom VR integration using provided events
|
||||
- Enable development console for full command access
|
||||
|
||||
### 🔮 Upcoming Features
|
||||
|
||||
#### Version 2.84 (Planned)
|
||||
- **Advanced Materials**: Support for composite and layered materials
|
||||
- **Weapon Customization**: Modular weapon system with attachments
|
||||
- **Enhanced VR Support**: Native VR motion controller integration
|
||||
- **Ray Tracing Integration**: Hardware ray tracing for precise ballistics
|
||||
|
||||
#### Version 2.85 (Planned)
|
||||
- **Destructible Integration**: Native support for destructible meshes
|
||||
- **Weather Effects**: Rain, snow, and fog impact on ballistics
|
||||
- **Advanced Networking**: Dedicated server optimizations
|
||||
- **Mobile Enhancements**: Further mobile platform optimizations
|
||||
|
||||
### 🙏 Contributors
|
||||
|
||||
Special thanks to the community members who contributed to this release:
|
||||
|
||||
- **Community Feedback**: Bug reports and feature suggestions
|
||||
- **Beta Testers**: Early access testing and validation
|
||||
- **Documentation**: Community-contributed examples and tutorials
|
||||
|
||||
### 📞 Support
|
||||
|
||||
For questions, issues, or feedback regarding this release:
|
||||
|
||||
- **Discord**: [Join our community](https://discord.gg/easyballistics)
|
||||
- **GitHub**: [Report issues](https://github.com/your-org/easyballistics/issues)
|
||||
- **Forums**: [Unreal Engine community](https://forums.unrealengine.com/)
|
||||
- **Email**: support@easyballistics.com
|
||||
|
||||
---
|
||||
|
||||
## Previous Releases
|
||||
|
||||
### [2.82.0] - 2024-10-15
|
||||
- Added G7 ballistic coefficient support
|
||||
- Improved atmospheric modeling
|
||||
- Fixed shotgun spread calculations
|
||||
- Enhanced multiplayer stability
|
||||
|
||||
### [2.81.0] - 2024-08-20
|
||||
- Introduced mathematical ballistics system
|
||||
- Added advanced penetration calculations
|
||||
- Improved object pooling performance
|
||||
- Fixed various collision detection issues
|
||||
|
||||
### [2.80.0] - 2024-06-10
|
||||
- Major architecture overhaul
|
||||
- Added UE5 compatibility
|
||||
- Implemented new material response system
|
||||
- Enhanced debugging tools
|
||||
|
||||
---
|
||||
|
||||
*For complete version history, see [GitHub Releases](https://github.com/your-org/easyballistics/releases)*
|
||||
@@ -0,0 +1,262 @@
|
||||
# Core Concepts
|
||||
|
||||
Understanding the fundamental concepts behind EasyBallistics will help you build more effective and realistic ballistic systems.
|
||||
|
||||
## System Architecture
|
||||
|
||||
EasyBallistics uses a modular architecture that separates concerns for maximum flexibility:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Projectile Definition] --> B[AEBBullet Actor]
|
||||
C[Surface Properties] --> D[Physical Materials]
|
||||
E[Interaction Rules] --> F[Material Response Map]
|
||||
|
||||
B --> G[UEBBallisticImpactComponent]
|
||||
D --> G
|
||||
F --> G
|
||||
|
||||
G --> H[Impact Events]
|
||||
G --> I[Penetration Calculation]
|
||||
G --> J[Ricochet Calculation]
|
||||
|
||||
K[UEBBarrel Component] --> B
|
||||
L[Mathematical Ballistics] --> G
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. Projectile System
|
||||
|
||||
**AEBBullet** is the main projectile actor that handles:
|
||||
- Physics simulation (velocity, acceleration, drag)
|
||||
- Collision detection and response
|
||||
- Atmospheric effects (air density, wind)
|
||||
- Network replication and prediction
|
||||
|
||||
### 2. Weapon System
|
||||
|
||||
**UEBBarrel** component manages:
|
||||
- Firing mechanics (single, auto, burst, gatling)
|
||||
- Ammunition management and reloading
|
||||
- Muzzle velocity and spread patterns
|
||||
- Shotgun support (multiple projectiles)
|
||||
|
||||
### 3. Impact System
|
||||
|
||||
**UEBBallisticImpactComponent** handles:
|
||||
- Material interaction calculations
|
||||
- Penetration depth determination
|
||||
- Ricochet probability and direction
|
||||
- Event broadcasting for custom responses
|
||||
|
||||
### 4. Material System
|
||||
|
||||
The material system consists of:
|
||||
- **Physical Materials**: UE5's built-in surface types
|
||||
- **Material Properties**: Ballistic characteristics (density, hardness)
|
||||
- **Material Response Maps**: Gameplay rules for interactions
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Firing Sequence
|
||||
|
||||
1. **Trigger Pull**: Player input or AI decision
|
||||
2. **Barrel Processing**: UEBBarrel handles fire rate, ammo consumption
|
||||
3. **Bullet Spawn**: AEBBullet created with initial velocity and properties
|
||||
4. **Physics Simulation**: Bullet moves through world with realistic physics
|
||||
5. **Impact Detection**: Collision system detects surface contact
|
||||
6. **Material Lookup**: System identifies surface material properties
|
||||
7. **Impact Calculation**: Penetration and ricochet calculations performed
|
||||
8. **Response Execution**: Events fired, effects spawned, physics applied
|
||||
|
||||
### Mathematical vs Artistic Modes
|
||||
|
||||
EasyBallistics supports two calculation approaches:
|
||||
|
||||
#### Mathematical Mode
|
||||
- Uses real-world ballistic formulas
|
||||
- Factors in bullet mass, velocity, material density
|
||||
- Provides scientifically accurate results
|
||||
- Best for simulations and realistic games
|
||||
|
||||
#### Artistic Mode
|
||||
- Uses simplified, game-designer-friendly values
|
||||
- Prioritizes fun and gameplay balance
|
||||
- Easily tweakable parameters
|
||||
- Best for arcade-style games
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Ballistic Coefficient (BC)
|
||||
|
||||
The BC determines how well a bullet cuts through air:
|
||||
- **G1 Model**: Standard for most bullets
|
||||
- **G7 Model**: More accurate for long-range, boat-tail bullets
|
||||
- Higher BC = less drag = flatter trajectory
|
||||
|
||||
```cpp
|
||||
// Configure ballistic coefficient
|
||||
BulletProperties.BallisticCoefficientG1 = 0.151f; // 55gr 5.56mm
|
||||
BulletProperties.UseG7Model = false; // Use G1 for this bullet
|
||||
```
|
||||
|
||||
### Sectional Density
|
||||
|
||||
The ratio of bullet weight to diameter squared:
|
||||
- Higher sectional density = better penetration
|
||||
- Automatically calculated if not specified
|
||||
- Critical for realistic penetration modeling
|
||||
|
||||
### Atmospheric Effects
|
||||
|
||||
Environmental factors that affect bullet flight:
|
||||
|
||||
```cpp
|
||||
// Configure atmospheric model
|
||||
Bullet->AtmosphereType = EEBAtmosphereType::AT_Earth; // Earth-like model
|
||||
Bullet->SeaLevelAirDensity = 1.21f; // kg/m³
|
||||
Bullet->SeaLevelAirTemperature = 20.0f; // °C
|
||||
```
|
||||
|
||||
### Penetration Mechanics
|
||||
|
||||
Penetration depends on multiple factors:
|
||||
- **Bullet energy**: Kinetic energy at impact
|
||||
- **Material resistance**: Hardness, density, tensile strength
|
||||
- **Impact angle**: Perpendicular impacts penetrate better
|
||||
- **Bullet construction**: Material and design affect penetration
|
||||
|
||||
### Ricochet Physics
|
||||
|
||||
Ricochet probability depends on:
|
||||
- **Impact angle**: Shallow angles increase ricochet chance
|
||||
- **Surface hardness**: Harder surfaces ricochet more
|
||||
- **Bullet velocity**: Higher velocity may overcome ricochet tendency
|
||||
- **Surface roughness**: Affects energy retention after ricochet
|
||||
|
||||
## Network Architecture
|
||||
|
||||
### Client-Server Model
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client
|
||||
participant S as Server
|
||||
participant O as Other Clients
|
||||
|
||||
C->>S: Fire Request
|
||||
S->>S: Validate & Spawn Bullet
|
||||
S->>O: Replicate Bullet
|
||||
C->>C: Predict Impact
|
||||
S->>O: Authoritative Impact
|
||||
Note over C,O: Reconciliation if needed
|
||||
```
|
||||
|
||||
### Prediction System
|
||||
|
||||
- **Client Prediction**: Immediate visual feedback
|
||||
- **Server Authority**: Final impact determination
|
||||
- **Reconciliation**: Smooth correction of prediction errors
|
||||
- **Lag Compensation**: Accounts for network latency
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Object Pooling
|
||||
|
||||
Bullets are expensive to create/destroy frequently:
|
||||
|
||||
```cpp
|
||||
// Enable pooling for high-rate weapons
|
||||
Bullet->EnablePooling = true;
|
||||
Bullet->MaxPoolSize = 100; // Pool up to 100 bullets
|
||||
```
|
||||
|
||||
### LOD System
|
||||
|
||||
Simulation quality scales with importance:
|
||||
- **High Detail**: Player weapons and nearby impacts
|
||||
- **Medium Detail**: Visible but distant projectiles
|
||||
- **Low Detail**: Off-screen or very distant bullets
|
||||
- **Culling**: Very far bullets may be removed entirely
|
||||
|
||||
### Trace Optimization
|
||||
|
||||
Multiple optimization layers:
|
||||
- **Collision Channels**: Separate channels for different trace types
|
||||
- **Trace Complexity**: Simple vs complex collision shapes
|
||||
- **Max Traces Per Step**: Limits traces per simulation frame
|
||||
- **Fixed Timestep**: Consistent simulation regardless of framerate
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Blueprint Integration
|
||||
|
||||
All major components expose Blueprint events:
|
||||
|
||||
```cpp
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FOnBallisticImpact,
|
||||
FVector, ImpactLocation,
|
||||
FVector, ImpactNormal,
|
||||
UPhysicalMaterial*, HitMaterial,
|
||||
float, PenetrationDepth,
|
||||
bool, bDidPenetrate);
|
||||
```
|
||||
|
||||
### C++ Extension Points
|
||||
|
||||
Key virtual functions for customization:
|
||||
- `UpdateVelocity()`: Custom physics calculations
|
||||
- `CollisionFilter()`: Custom hit validation
|
||||
- `OnImpact()`: Custom impact processing
|
||||
|
||||
### Asset Pipeline Integration
|
||||
|
||||
- **Content Browser**: Custom asset creation menus
|
||||
- **Property Editor**: Specialized property panels
|
||||
- **Material Editor**: Ballistic properties in Physical Materials
|
||||
- **Blueprint Editor**: Component-specific details panels
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Asset Organization
|
||||
|
||||
```
|
||||
Content/
|
||||
├── Ballistics/
|
||||
│ ├── BulletProperties/
|
||||
│ │ ├── Rifle/
|
||||
│ │ ├── Pistol/
|
||||
│ │ └── Shotgun/
|
||||
│ ├── MaterialMaps/
|
||||
│ │ ├── Military/
|
||||
│ │ ├── Civilian/
|
||||
│ │ └── SciFi/
|
||||
│ └── PhysicalMaterials/
|
||||
│ ├── Metals/
|
||||
│ ├── Organic/
|
||||
│ └── Synthetic/
|
||||
```
|
||||
|
||||
### Performance Guidelines
|
||||
|
||||
1. **Use Pooling**: Always enable for rapid-fire weapons
|
||||
2. **Appropriate Complexity**: Match simulation detail to gameplay needs
|
||||
3. **Material Optimization**: Group similar materials in response maps
|
||||
4. **Network Optimization**: Use reliable replication sparingly
|
||||
|
||||
### Debugging Workflow
|
||||
|
||||
1. **Visual Debugging**: Enable bullet trails and impact markers
|
||||
2. **Performance Profiling**: Monitor trace counts and simulation time
|
||||
3. **Network Analysis**: Check replication bandwidth and prediction accuracy
|
||||
4. **Asset Validation**: Verify material assignments and property ranges
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you understand the core concepts:
|
||||
|
||||
1. [Asset Types](asset-types) - Deep dive into each asset type
|
||||
2. [Ballistics System](ballistics-system) - Detailed physics explanations
|
||||
3. [Physical Materials](physical-materials) - Material system integration
|
||||
4. [Component Guide](../components/bullet-actor) - Component-specific documentation
|
||||
@@ -0,0 +1,157 @@
|
||||
# Installation
|
||||
|
||||
This guide will walk you through installing EasyBallistics in your Unreal Engine project.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Unreal Engine 5.6.0** or later
|
||||
- **Visual Studio 2022** (for C++ projects)
|
||||
- **Git** (for version control)
|
||||
|
||||
## Installation Methods
|
||||
|
||||
### Method 1: Marketplace Installation (Recommended)
|
||||
|
||||
1. Open the **Epic Games Launcher**
|
||||
2. Navigate to the **Marketplace**
|
||||
3. Search for "EasyBallistics"
|
||||
4. Click **Add to Project** or **Install to Engine**
|
||||
5. Select your target project
|
||||
6. Click **Install**
|
||||
|
||||
### Method 2: Manual Installation
|
||||
|
||||
1. Download the plugin from the marketplace or GitHub
|
||||
2. Extract the plugin to your project's `Plugins` folder:
|
||||
```
|
||||
YourProject/
|
||||
├── Plugins/
|
||||
│ └── EasyBallistics/
|
||||
│ ├── Source/
|
||||
│ ├── Resources/
|
||||
│ └── EasyBallistics.uplugin
|
||||
```
|
||||
3. Regenerate project files:
|
||||
- Right-click your `.uproject` file
|
||||
- Select **Generate Visual Studio project files**
|
||||
|
||||
### Method 3: Git Submodule (Advanced)
|
||||
|
||||
```bash
|
||||
# Navigate to your project directory
|
||||
cd YourProject/Plugins
|
||||
|
||||
# Add as submodule
|
||||
git submodule add https://github.com/your-org/easyballistics.git EasyBallistics
|
||||
|
||||
# Initialize and update
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Enabling the Plugin
|
||||
|
||||
1. Open your project in Unreal Engine
|
||||
2. Go to **Edit → Plugins**
|
||||
3. Search for "EasyBallistics"
|
||||
4. Check the **Enabled** checkbox
|
||||
5. Click **Restart Now** when prompted
|
||||
|
||||

|
||||
|
||||
## Verifying Installation
|
||||
|
||||
### Check Plugin Status
|
||||
|
||||
1. Open **Edit → Plugins**
|
||||
2. Verify EasyBallistics shows as **Enabled**
|
||||
3. Check for any error messages
|
||||
|
||||
### Test Basic Functionality
|
||||
|
||||
1. In the Content Browser, right-click
|
||||
2. Look for **Ballistics** category in the asset creation menu
|
||||
3. You should see:
|
||||
- Bullet Properties
|
||||
- Material Response Map
|
||||
|
||||

|
||||
|
||||
### Verify Components
|
||||
|
||||
1. Create a new Actor Blueprint
|
||||
2. Add Component → Search "Ballistic"
|
||||
3. You should find:
|
||||
- **EB Barrel** (weapon barrel component)
|
||||
- **EB Ballistic Impact Component** (impact handling)
|
||||
|
||||
## Project Configuration
|
||||
|
||||
### Build Configuration
|
||||
|
||||
Add to your project's `DefaultEngine.ini`:
|
||||
|
||||
```ini
|
||||
[/Script/Engine.Engine]
|
||||
+ActiveGameNameRedirects=(OldGameName="EasyBallistics",NewGameName="/Script/EasyBallistics")
|
||||
+ActiveGameNameRedirects=(OldGameName="EasyBallisticsEditor",NewGameName="/Script/EasyBallisticsEditor")
|
||||
```
|
||||
|
||||
### Module Dependencies
|
||||
|
||||
If using C++, add to your project's `Build.cs` file:
|
||||
|
||||
```cpp
|
||||
PublicDependencyModuleNames.AddRange(new string[] {
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"EasyBallistics" // Add this line
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Plugin Not Appearing
|
||||
- Ensure you're using UE 5.6 or later
|
||||
- Check if the plugin files are in the correct location
|
||||
- Regenerate project files
|
||||
|
||||
#### Compilation Errors
|
||||
- Verify Visual Studio 2022 is installed
|
||||
- Check that all dependencies are met
|
||||
- Try a clean rebuild
|
||||
|
||||
#### Missing Components
|
||||
- Restart the editor after enabling the plugin
|
||||
- Clear the derived data cache: **Edit → Developer → Derived Data → Clear**
|
||||
|
||||
### Error Messages
|
||||
|
||||
#### "Module 'EasyBallistics' could not be loaded"
|
||||
1. Check your UE version compatibility
|
||||
2. Verify the plugin is enabled
|
||||
3. Regenerate project files
|
||||
4. Clean and rebuild the project
|
||||
|
||||
#### "Failed to load because module 'EasyBallistics' could not be found"
|
||||
1. Ensure the plugin is in the `Plugins` folder
|
||||
2. Check the `.uplugin` file is valid
|
||||
3. Verify file permissions
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once installation is complete:
|
||||
|
||||
1. [Quick Start Guide](quick-start) - Create your first ballistic weapon
|
||||
2. [Basic Setup](basic-setup) - Configure materials and responses
|
||||
3. [Core Concepts](../core-concepts/overview) - Understand the system architecture
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues during installation:
|
||||
|
||||
- Check the [Troubleshooting](../troubleshooting) guide
|
||||
- Visit our [Discord community](https://discord.gg/easyballistics)
|
||||
- Submit an issue on [GitHub](https://github.com/your-org/easyballistics/issues)
|
||||
@@ -0,0 +1,247 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get up and running with EasyBallistics in under 10 minutes! This guide will walk you through creating your first ballistic weapon system.
|
||||
|
||||
## Overview
|
||||
|
||||
In this quick start, you'll:
|
||||
1. Create bullet properties
|
||||
2. Set up a material response map
|
||||
3. Create a simple weapon
|
||||
4. Test ballistic impacts
|
||||
|
||||
## Step 1: Create Bullet Properties
|
||||
|
||||
First, let's define the characteristics of our projectile.
|
||||
|
||||
### Create the Asset
|
||||
|
||||
1. In the Content Browser, **right-click** → **Ballistics** → **Bullet Properties**
|
||||
2. Name it `BP_556_NATO_Properties`
|
||||
3. **Double-click** to open the editor
|
||||
|
||||
### Configure Properties
|
||||
|
||||
Set these values for a 5.56x45mm NATO round:
|
||||
|
||||
```yaml
|
||||
Bullet Properties:
|
||||
Grain Weight: 55.0
|
||||
Diameter Inches: 0.224
|
||||
Length Inches: 0.825
|
||||
Bullet Type: Full Metal Jacket
|
||||
Bullet Material: Copper 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
|
||||
Expansion Velocity Threshold: 1800.0
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Step 2: Set Up Physical Materials
|
||||
|
||||
Configure materials that bullets will interact with.
|
||||
|
||||
### Create Steel Material
|
||||
|
||||
1. Content Browser → **right-click** → **Physics** → **Physical Material**
|
||||
2. Name it `PM_Steel`
|
||||
3. Open the material editor
|
||||
4. In the **Ballistics** section:
|
||||
- Click **Create New** to generate ballistic properties
|
||||
- The system will auto-configure steel properties
|
||||
|
||||
### Create Wood Material
|
||||
|
||||
1. Create another Physical Material: `PM_Wood`
|
||||
2. In the **Ballistics** section:
|
||||
- Click **Create New**
|
||||
- The system will detect "wood" in the name and configure accordingly
|
||||
|
||||
## Step 3: Create Material Response Map
|
||||
|
||||
Define how bullets interact with different materials.
|
||||
|
||||
### Create the Asset
|
||||
|
||||
1. Content Browser → **right-click** → **Ballistics** → **Material Response Map**
|
||||
2. Name it `MRM_Default`
|
||||
3. Open the editor
|
||||
|
||||
### Configure Responses
|
||||
|
||||
Add entries for your materials:
|
||||
|
||||
```yaml
|
||||
Steel (PM_Steel):
|
||||
Penetration Depth Multiplier: 0.3
|
||||
Ricochet Probability Multiplier: 2.0
|
||||
Ricochet Restitution: 0.8
|
||||
Never Penetrate: false
|
||||
Never Ricochet: false
|
||||
|
||||
Wood (PM_Wood):
|
||||
Penetration Depth Multiplier: 2.0
|
||||
Ricochet Probability Multiplier: 0.1
|
||||
Ricochet Restitution: 0.2
|
||||
Never Penetrate: false
|
||||
Never Ricochet: false
|
||||
```
|
||||
|
||||
## Step 4: Create a Simple Weapon
|
||||
|
||||
Now let's create a weapon that fires ballistic projectiles.
|
||||
|
||||
### Create Weapon Blueprint
|
||||
|
||||
1. Create **Actor Blueprint**: `BP_SimpleRifle`
|
||||
2. Add a **Static Mesh Component** for the weapon model
|
||||
3. Add an **EB Barrel Component**
|
||||
|
||||
### Configure the Barrel
|
||||
|
||||
Select the **EB Barrel** component and set:
|
||||
|
||||
```yaml
|
||||
Ammunition:
|
||||
Bullet Class: EBBullet (from dropdown)
|
||||
Bullet Properties Asset: BP_556_NATO_Properties
|
||||
Material Response Map: MRM_Default
|
||||
|
||||
Ballistics:
|
||||
Muzzle Velocity Min: 3100
|
||||
Muzzle Velocity Max: 3200
|
||||
Use Mathematical Physics: true
|
||||
Use New Impact System: true
|
||||
|
||||
Fire Mode:
|
||||
Fire Mode: Semiauto
|
||||
Rounds Per Minute: 600
|
||||
```
|
||||
|
||||
### Add Firing Logic
|
||||
|
||||
In the **Event Graph**, add this simple firing setup:
|
||||
|
||||
```cpp
|
||||
// On Input Action Fire (or your input event)
|
||||
Event InputAction Fire
|
||||
↓
|
||||
Branch (Can Fire?)
|
||||
↓ True
|
||||
Call Function: Fire Single Shot
|
||||
↓
|
||||
Set Timer by Function Name
|
||||
- Function Name: "Reset Fire Cooldown"
|
||||
- Time: 0.1 (for 600 RPM)
|
||||
```
|
||||
|
||||
## Step 5: Set Up Target Objects
|
||||
|
||||
Create objects to shoot at with proper materials.
|
||||
|
||||
### Create Target Blueprint
|
||||
|
||||
1. Create **Actor Blueprint**: `BP_Target`
|
||||
2. Add **Static Mesh Component** (use a cube or custom model)
|
||||
3. Set the **Physical Material** to either `PM_Steel` or `PM_Wood`
|
||||
4. Enable **Collision** and **Simulate Physics**
|
||||
|
||||
### Add Impact Response
|
||||
|
||||
Add an **EB Ballistic Impact Component** to the target:
|
||||
|
||||
```cpp
|
||||
// In the target's Event Graph
|
||||
OnBallisticImpact
|
||||
↓
|
||||
Branch (Did Penetrate?)
|
||||
↓ True: Spawn Penetration Effect
|
||||
↓ False: Spawn Impact Effect
|
||||
|
||||
OnBallisticRicochet
|
||||
↓
|
||||
Spawn Ricochet Effect
|
||||
↓
|
||||
Play Ricochet Sound
|
||||
```
|
||||
|
||||
## Step 6: Test the System
|
||||
|
||||
Time to see your ballistic system in action!
|
||||
|
||||
### Testing Setup
|
||||
|
||||
1. Place `BP_SimpleRifle` in your level
|
||||
2. Place several `BP_Target` actors with different materials
|
||||
3. Set up a **Player Controller** or **Pawn** to control the weapon
|
||||
4. **Play** the level
|
||||
|
||||
### What to Look For
|
||||
|
||||
- **Penetration**: Bullets should penetrate wood more easily than steel
|
||||
- **Ricochet**: Bullets should ricochet off steel at shallow angles
|
||||
- **Material Response**: Different materials should show different impact behaviors
|
||||
- **Physics**: Targets should receive impulse and move when hit
|
||||
|
||||
### Debug Features
|
||||
|
||||
Enable these for testing:
|
||||
|
||||
```yaml
|
||||
Bullet Debug Options:
|
||||
Debug Enabled: true
|
||||
Debug Trail Time: 2.0
|
||||
Debug Trail Color Fast: Green
|
||||
Debug Trail Color Slow: Red
|
||||
```
|
||||
|
||||
You'll see colored trails showing bullet paths!
|
||||
|
||||
## Expected Results
|
||||
|
||||
After completing this setup, you should have:
|
||||
|
||||
✅ **Realistic Ballistics**: Bullets affected by gravity and air resistance
|
||||
✅ **Material Interactions**: Different penetration/ricochet on steel vs wood
|
||||
✅ **Impact Events**: Targets respond to bullet impacts
|
||||
✅ **Debug Visualization**: Visible bullet trails for testing
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Bullets Not Spawning
|
||||
- Check that `Bullet Class` is set to `EBBullet`
|
||||
- Verify `Bullet Properties Asset` is assigned
|
||||
- Ensure the barrel component is properly configured
|
||||
|
||||
### No Material Response
|
||||
- Verify targets have **Physical Materials** assigned
|
||||
- Check that materials are added to the **Material Response Map**
|
||||
- Ensure `Use New Impact System` is enabled
|
||||
|
||||
### Poor Performance
|
||||
- Enable **Object Pooling** in bullet settings
|
||||
- Reduce **Max Traces Per Step** if needed
|
||||
- Consider using **Fixed Step** simulation for consistency
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a basic system working:
|
||||
|
||||
1. [Asset Creation Guide](../assets/bullet-properties) - Create more bullet types
|
||||
2. [Material Setup](../assets/physical-material-integration) - Advanced material configuration
|
||||
3. [Advanced Ballistics](../tutorials/advanced-ballistics) - Atmospheric effects and drag curves
|
||||
4. [Networking](../networking/overview) - Set up multiplayer ballistics
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Join our [Discord](https://discord.gg/easyballistics) for real-time help
|
||||
- Check the [API Reference](../api/overview) for detailed function documentation
|
||||
- Browse [Tutorials](../tutorials/first-weapon) for more advanced setups
|
||||
@@ -0,0 +1,81 @@
|
||||
# EasyBallistics Documentation
|
||||
|
||||
Welcome to **EasyBallistics**, the most advanced ballistics simulation plugin for Unreal Engine 5.6!
|
||||
|
||||
## What is EasyBallistics?
|
||||
|
||||
EasyBallistics is a comprehensive ballistics simulation system that brings realistic projectile physics to your Unreal Engine projects. Whether you're developing a tactical shooter, simulation, or any game requiring realistic bullet behavior, EasyBallistics provides the tools you need.
|
||||
|
||||
## Key Features
|
||||
|
||||
### 🎯 **Realistic Ballistics**
|
||||
- Atmospheric effects (air density, temperature, pressure)
|
||||
- Wind simulation and drag calculations
|
||||
- Ballistic coefficient support (G1 and G7 models)
|
||||
- Mathematical penetration and ricochet calculations
|
||||
|
||||
### 🏗️ **Modular Asset System**
|
||||
- **Bullet Properties**: Define projectile characteristics
|
||||
- **Material Response Maps**: Configure surface interactions
|
||||
- **Physical Material Integration**: Seamless UE5 workflow
|
||||
- **Ballistic Impact Component**: Event-driven impact handling
|
||||
|
||||
### 🌐 **Multiplayer Ready**
|
||||
- Full replication support
|
||||
- Client-side prediction
|
||||
- Optimized for high-rate fire scenarios
|
||||
- Network-efficient pooling system
|
||||
|
||||
### ⚡ **Performance Optimized**
|
||||
- Object pooling for projectiles
|
||||
- Configurable simulation complexity
|
||||
- LOD system for distant projectiles
|
||||
- Multi-threaded calculations
|
||||
|
||||
## Quick Start
|
||||
|
||||
```cpp
|
||||
// Create a bullet with realistic ballistics
|
||||
AEBBullet* Bullet = GetWorld()->SpawnActor<AEBBullet>();
|
||||
Bullet->BulletPropertiesAsset = MyBulletProperties;
|
||||
Bullet->UseNewImpactSystem = true;
|
||||
Bullet->UseMathematicalPhysics = true;
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Bullet Properties] --> D[EBBullet Actor]
|
||||
B[Material Properties] --> E[Physical Materials]
|
||||
C[Material Response Map] --> D
|
||||
E --> F[Ballistic Impact Component]
|
||||
D --> F
|
||||
F --> G[Impact Events]
|
||||
F --> H[Penetration Calculation]
|
||||
F --> I[Ricochet Calculation]
|
||||
```
|
||||
|
||||
## System Requirements
|
||||
|
||||
- **Unreal Engine**: 5.6.0 or later
|
||||
- **Platforms**: Windows, Mac, Linux, Android
|
||||
- **Network**: Full multiplayer support
|
||||
- **Performance**: Optimized for 60+ FPS with hundreds of projectiles
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. [Installation Guide](getting-started/installation) - Install the plugin
|
||||
2. [Quick Start](getting-started/quick-start) - Create your first weapon
|
||||
3. [Basic Setup](getting-started/basic-setup) - Configure materials and responses
|
||||
|
||||
## Support
|
||||
|
||||
- 📧 **Email**: support@easyballistics.com
|
||||
- 💬 **Discord**: [Join our community](https://discord.gg/easyballistics)
|
||||
- 🐛 **Issues**: [GitHub Issues](https://github.com/your-org/easyballistics/issues)
|
||||
- 📚 **Forums**: [Unreal Engine Forums](https://forums.unrealengine.com/)
|
||||
|
||||
---
|
||||
|
||||
*Built with ❤️ for the Unreal Engine community*
|
||||
@@ -0,0 +1,459 @@
|
||||
# Troubleshooting Guide
|
||||
|
||||
Common issues and solutions for EasyBallistics.
|
||||
|
||||
## Installation Issues
|
||||
|
||||
### Plugin Not Appearing in Plugin Manager
|
||||
|
||||
**Symptoms:**
|
||||
- EasyBallistics doesn't show up in Edit → Plugins
|
||||
- No Ballistics category in asset creation menu
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check UE Version Compatibility**
|
||||
```
|
||||
Required: Unreal Engine 5.6.0 or later
|
||||
Current: Check Help → About Unreal Editor
|
||||
```
|
||||
|
||||
2. **Verify Plugin Location**
|
||||
```
|
||||
Correct path: YourProject/Plugins/EasyBallistics/
|
||||
Check for: EasyBallistics.uplugin file
|
||||
```
|
||||
|
||||
3. **Regenerate Project Files**
|
||||
- Right-click your `.uproject` file
|
||||
- Select "Generate Visual Studio project files"
|
||||
- Restart Unreal Engine
|
||||
|
||||
4. **Clear Derived Data Cache**
|
||||
- Edit → Developer → Derived Data
|
||||
- Click "Clear"
|
||||
- Restart editor
|
||||
|
||||
### Compilation Errors
|
||||
|
||||
**Error: "Module 'EasyBallistics' could not be loaded"**
|
||||
|
||||
```cpp
|
||||
// Add to YourProject.Build.cs
|
||||
PublicDependencyModuleNames.AddRange(new string[] {
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"EasyBallistics" // Add this line
|
||||
});
|
||||
```
|
||||
|
||||
**Error: "Failed to import class 'EBBullet'"**
|
||||
|
||||
1. Ensure plugin is enabled
|
||||
2. Check that EasyBallistics.Build.cs exists
|
||||
3. Verify all source files are present
|
||||
4. Clean and rebuild project
|
||||
|
||||
## Runtime Issues
|
||||
|
||||
### Bullets Not Spawning
|
||||
|
||||
**Symptoms:**
|
||||
- Fire input triggers but no bullets appear
|
||||
- No muzzle flash or effects
|
||||
|
||||
**Debugging Steps:**
|
||||
|
||||
1. **Check Barrel Configuration**
|
||||
```cpp
|
||||
// Verify these are set:
|
||||
BulletClass = AEBBullet::StaticClass()
|
||||
BulletPropertiesAsset = Valid asset
|
||||
MuzzleVelocityMin > 0
|
||||
MuzzleVelocityMax > 0
|
||||
```
|
||||
|
||||
2. **Verify Spawning Logic**
|
||||
```cpp
|
||||
// Add debug logging
|
||||
UE_LOG(LogTemp, Warning, TEXT("Attempting to fire bullet"));
|
||||
|
||||
if (!BulletClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("BulletClass is null!"));
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
3. **Check World Reference**
|
||||
```cpp
|
||||
// Ensure valid world context
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("No valid world for bullet spawn"));
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### Bullets Spawning But Not Moving
|
||||
|
||||
**Symptoms:**
|
||||
- Bullets appear at muzzle but don't travel
|
||||
- Static bullet actors in world
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check Initial Velocity**
|
||||
```cpp
|
||||
// Bullet velocity should be set on spawn
|
||||
Bullet->Velocity = MuzzleDirection * MuzzleVelocity;
|
||||
|
||||
// Verify velocity is not zero
|
||||
if (Bullet->Velocity.IsNearlyZero())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Bullet velocity is zero!"));
|
||||
}
|
||||
```
|
||||
|
||||
2. **Verify Actor Tick**
|
||||
```cpp
|
||||
// In bullet constructor, ensure:
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
```
|
||||
|
||||
3. **Check Physics Settings**
|
||||
```cpp
|
||||
// Ensure physics simulation is enabled
|
||||
Bullet->SetActorEnableCollision(true);
|
||||
```
|
||||
|
||||
### No Impact Detection
|
||||
|
||||
**Symptoms:**
|
||||
- Bullets pass through objects without impact
|
||||
- No impact events or effects
|
||||
|
||||
**Debugging Steps:**
|
||||
|
||||
1. **Check Collision Settings**
|
||||
```cpp
|
||||
// Verify collision channel
|
||||
TraceChannel = ECC_WorldStatic // or appropriate channel
|
||||
TraceComplex = true // for complex collision
|
||||
```
|
||||
|
||||
2. **Verify Target Collision**
|
||||
- Ensure target has collision enabled
|
||||
- Check collision response to bullet trace channel
|
||||
- Verify mesh has collision geometry
|
||||
|
||||
3. **Debug Visualization**
|
||||
```cpp
|
||||
// Enable debug traces
|
||||
Bullet->DebugEnabled = true;
|
||||
Bullet->DebugTrailTime = 2.0f;
|
||||
```
|
||||
|
||||
### Incorrect Penetration/Ricochet Behavior
|
||||
|
||||
**Symptoms:**
|
||||
- Bullets always penetrate or never penetrate
|
||||
- Ricochet behavior doesn't match material properties
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check Material Response Map**
|
||||
```cpp
|
||||
// Verify material is in response map
|
||||
if (!MaterialResponseMap->Map.Contains(PhysicalMaterial))
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Material not found in response map"));
|
||||
}
|
||||
```
|
||||
|
||||
2. **Verify Physical Material Assignment**
|
||||
- Check target mesh has Physical Material assigned
|
||||
- Ensure Physical Material is not null in hit result
|
||||
|
||||
3. **Check New Impact System**
|
||||
```cpp
|
||||
// Ensure new system is enabled
|
||||
Bullet->UseNewImpactSystem = true;
|
||||
|
||||
// Verify component exists
|
||||
if (!Bullet->BallisticImpactComponent)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("BallisticImpactComponent is null"));
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Issues
|
||||
|
||||
### Low Frame Rate with Many Bullets
|
||||
|
||||
**Symptoms:**
|
||||
- FPS drops when firing rapidly
|
||||
- Hitches during bullet simulation
|
||||
|
||||
**Optimization Steps:**
|
||||
|
||||
1. **Enable Object Pooling**
|
||||
```cpp
|
||||
Bullet->EnablePooling = true;
|
||||
Bullet->MaxPoolSize = 50; // Adjust based on needs
|
||||
```
|
||||
|
||||
2. **Reduce Trace Complexity**
|
||||
```cpp
|
||||
// Reduce traces per step
|
||||
Bullet->MaxTracesPerStep = 4; // Down from 8
|
||||
|
||||
// Use simple collision for distant bullets
|
||||
Bullet->TraceComplex = false;
|
||||
```
|
||||
|
||||
3. **Implement LOD System**
|
||||
```cpp
|
||||
// Distance-based quality scaling
|
||||
float Distance = FVector::Dist(Bullet->GetActorLocation(), PlayerLocation);
|
||||
if (Distance > 5000.0f)
|
||||
{
|
||||
Bullet->FixedStepSeconds = 0.2f; // Lower frequency
|
||||
}
|
||||
```
|
||||
|
||||
4. **Profile Performance**
|
||||
```cpp
|
||||
// Use built-in profiling
|
||||
stat game
|
||||
stat engine
|
||||
stat collision
|
||||
```
|
||||
|
||||
### Memory Usage Issues
|
||||
|
||||
**Symptoms:**
|
||||
- Increasing memory usage over time
|
||||
- Out of memory errors during extended play
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check Pool Cleanup**
|
||||
```cpp
|
||||
// Ensure bullets are properly deactivated
|
||||
void AEBBullet::Deactivate()
|
||||
{
|
||||
SetActorHiddenInGame(true);
|
||||
SetActorEnableCollision(false);
|
||||
DeactivateToPool(); // Return to pool
|
||||
}
|
||||
```
|
||||
|
||||
2. **Limit Pool Sizes**
|
||||
```cpp
|
||||
// Prevent unlimited pool growth
|
||||
Bullet->MaxPoolSize = 100; // Hard limit
|
||||
```
|
||||
|
||||
3. **Monitor Actor Count**
|
||||
```cpp
|
||||
// Debug actor counts
|
||||
UE_LOG(LogTemp, Warning, TEXT("Active bullets: %d"),
|
||||
GetWorld()->GetActorCount<AEBBullet>());
|
||||
```
|
||||
|
||||
## Network Issues
|
||||
|
||||
### Bullets Not Replicating
|
||||
|
||||
**Symptoms:**
|
||||
- Bullets visible on server but not clients
|
||||
- Desync between players
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check Replication Settings**
|
||||
```cpp
|
||||
// In bullet constructor
|
||||
bReplicates = true;
|
||||
SetReplicateMovement(true);
|
||||
```
|
||||
|
||||
2. **Verify Network Role**
|
||||
```cpp
|
||||
// Only spawn bullets on server
|
||||
if (HasAuthority())
|
||||
{
|
||||
SpawnBullet();
|
||||
}
|
||||
```
|
||||
|
||||
3. **Check Bandwidth Usage**
|
||||
```
|
||||
net.PackageMap.DebugObject BulletClass
|
||||
stat net
|
||||
```
|
||||
|
||||
### Prediction Issues
|
||||
|
||||
**Symptoms:**
|
||||
- Laggy bullet impacts
|
||||
- Bullets appear to "jump" position
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Enable Client Prediction**
|
||||
```cpp
|
||||
// Allow client-side impact prediction
|
||||
Bullet->bNetUseOwnerRelevancy = true;
|
||||
```
|
||||
|
||||
2. **Adjust Prediction Settings**
|
||||
```cpp
|
||||
// Fine-tune prediction parameters
|
||||
Bullet->NetCullDistanceSquared = 10000000.0f; // 1000m
|
||||
```
|
||||
|
||||
## Blueprint Issues
|
||||
|
||||
### Events Not Firing
|
||||
|
||||
**Symptoms:**
|
||||
- OnBallisticImpact events don't trigger
|
||||
- Custom impact logic not executing
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check Event Binding**
|
||||
```cpp
|
||||
// Verify event is bound in BeginPlay
|
||||
BallisticImpactComponent->OnBallisticImpact.AddDynamic(
|
||||
this, &AMyActor::HandleImpact);
|
||||
```
|
||||
|
||||
2. **Verify Component Reference**
|
||||
- Ensure BallisticImpactComponent exists on actor
|
||||
- Check component is properly initialized
|
||||
|
||||
3. **Debug Event Calls**
|
||||
```cpp
|
||||
// Add logging to verify events
|
||||
void HandleImpact(/* parameters */)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Impact event received"));
|
||||
}
|
||||
```
|
||||
|
||||
### Asset References Not Working
|
||||
|
||||
**Symptoms:**
|
||||
- Bullet Properties Asset shows as None
|
||||
- Material Response Map not found
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check Asset Paths**
|
||||
```cpp
|
||||
// Verify asset references are valid
|
||||
if (!BulletPropertiesAsset)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("BulletPropertiesAsset is null"));
|
||||
}
|
||||
```
|
||||
|
||||
2. **Recreate Asset References**
|
||||
- Delete and reassign asset references
|
||||
- Check for missing asset redirects
|
||||
|
||||
## Debug Tools
|
||||
|
||||
### Enable Debug Visualization
|
||||
|
||||
```cpp
|
||||
// In bullet settings
|
||||
DebugEnabled = true
|
||||
DebugTrailTime = 2.0
|
||||
DebugTrailWidth = 1.0
|
||||
DebugTrailColorFast = Green
|
||||
DebugTrailColorSlow = Red
|
||||
```
|
||||
|
||||
### Console Commands
|
||||
|
||||
```
|
||||
// Show bullet debug info
|
||||
showdebug collision
|
||||
showdebug physics
|
||||
|
||||
// Network debugging
|
||||
net.PackageMap.DebugObject AEBBullet
|
||||
stat net
|
||||
|
||||
// Performance profiling
|
||||
stat game
|
||||
stat collision
|
||||
stat memory
|
||||
```
|
||||
|
||||
### Logging Categories
|
||||
|
||||
Add to `DefaultEngine.ini`:
|
||||
|
||||
```ini
|
||||
[Core.Log]
|
||||
LogEasyBallistics=VeryVerbose
|
||||
LogNetPlayerMovement=Verbose
|
||||
LogCollision=Warning
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
### Before Reporting Issues
|
||||
|
||||
1. **Check Documentation**
|
||||
- Review relevant API documentation
|
||||
- Check tutorials for similar setups
|
||||
|
||||
2. **Gather Information**
|
||||
- UE version and platform
|
||||
- Plugin version
|
||||
- Minimal reproduction steps
|
||||
- Error logs and stack traces
|
||||
|
||||
3. **Search Existing Issues**
|
||||
- Check GitHub issues
|
||||
- Search community forums
|
||||
|
||||
### Support Channels
|
||||
|
||||
- **GitHub Issues**: [Report bugs and feature requests](https://github.com/your-org/easyballistics/issues)
|
||||
- **Discord**: [Real-time community support](https://discord.gg/easyballistics)
|
||||
- **Forums**: [Unreal Engine community forums](https://forums.unrealengine.com/)
|
||||
- **Email**: support@easyballistics.com
|
||||
|
||||
### Issue Template
|
||||
|
||||
When reporting issues, include:
|
||||
|
||||
```
|
||||
**Environment:**
|
||||
- UE Version: 5.6.0
|
||||
- Platform: Windows 10
|
||||
- Plugin Version: 2.83
|
||||
|
||||
**Issue Description:**
|
||||
Brief description of the problem
|
||||
|
||||
**Steps to Reproduce:**
|
||||
1. Step one
|
||||
2. Step two
|
||||
3. Expected vs actual behavior
|
||||
|
||||
**Logs:**
|
||||
Attach relevant log files
|
||||
|
||||
**Additional Context:**
|
||||
Screenshots, videos, or other helpful information
|
||||
```
|
||||
@@ -0,0 +1,146 @@
|
||||
// @ts-check
|
||||
// `@type` JSDoc annotations allow editor autocompletion and type checking
|
||||
// (when paired with `@ts-check`).
|
||||
// There are various equivalent ways to declare your Docusaurus config.
|
||||
// See: https://docusaurus.io/docs/api/docusaurus-config
|
||||
|
||||
import {themes as prismThemes} from 'prism-react-renderer';
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'EasyBallistics',
|
||||
tagline: 'Advanced Ballistics Simulation for Unreal Engine 5.6',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
// Set the production url of your site here
|
||||
url: 'https://your-docusaurus-site.example.com',
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: '/',
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: 'mookie', // Usually your GitHub org/user name.
|
||||
projectName: 'easyballistics', // Usually your repo name.
|
||||
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
|
||||
// Even if you don't use internationalization, you can use this field to set
|
||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||
// may want to set it to `zh-Hans`.
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
({
|
||||
docs: {
|
||||
routeBasePath: '/',
|
||||
sidebarPath: './sidebars.js',
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl:
|
||||
'https://github.com/your-org/easyballistics/tree/main/docs/',
|
||||
},
|
||||
blog: false,
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
}),
|
||||
],
|
||||
],
|
||||
|
||||
markdown: {
|
||||
mermaid: true,
|
||||
},
|
||||
themes: ['@docusaurus/theme-mermaid'],
|
||||
|
||||
themeConfig:
|
||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||
({
|
||||
// Replace with your project's social card
|
||||
image: 'img/easyballistics-social-card.jpg',
|
||||
navbar: {
|
||||
title: 'EasyBallistics',
|
||||
logo: {
|
||||
alt: 'EasyBallistics Logo',
|
||||
src: 'img/logo.svg',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'tutorialSidebar',
|
||||
position: 'left',
|
||||
label: 'Documentation',
|
||||
},
|
||||
{
|
||||
href: 'https://www.unrealengine.com/marketplace/en-US/product/easyballistics',
|
||||
label: 'Marketplace',
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/your-org/easyballistics',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
links: [
|
||||
{
|
||||
title: 'Documentation',
|
||||
items: [
|
||||
{
|
||||
label: 'Getting Started',
|
||||
to: '/getting-started',
|
||||
},
|
||||
{
|
||||
label: 'API Reference',
|
||||
to: '/api',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Community',
|
||||
items: [
|
||||
{
|
||||
label: 'Discord',
|
||||
href: 'https://discord.gg/unrealengine',
|
||||
},
|
||||
{
|
||||
label: 'Unreal Engine Forums',
|
||||
href: 'https://forums.unrealengine.com/',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'More',
|
||||
items: [
|
||||
{
|
||||
label: 'Marketplace',
|
||||
href: 'https://www.unrealengine.com/marketplace/en-US/product/easyballistics',
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/your-org/easyballistics',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Mookie. Built with Docusaurus.`,
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.github,
|
||||
darkTheme: prismThemes.dracula,
|
||||
additionalLanguages: ['cpp', 'blueprint'],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "easyballistics-docs",
|
||||
"version": "2.83.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.0.0",
|
||||
"@docusaurus/preset-classic": "^3.0.0",
|
||||
"@docusaurus/theme-mermaid": "^3.0.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.1.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.0.0",
|
||||
"@docusaurus/tsconfig": "^3.0.0",
|
||||
"@docusaurus/types": "^3.0.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 3 chrome version",
|
||||
"last 3 firefox version",
|
||||
"last 5 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [
|
||||
'intro',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
'getting-started/installation',
|
||||
'getting-started/quick-start',
|
||||
'getting-started/basic-setup',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Core Concepts',
|
||||
items: [
|
||||
'core-concepts/overview',
|
||||
'core-concepts/ballistics-system',
|
||||
'core-concepts/asset-types',
|
||||
'core-concepts/physical-materials',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Asset Creation',
|
||||
items: [
|
||||
'assets/bullet-properties',
|
||||
'assets/material-response-maps',
|
||||
'assets/physical-material-integration',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Components',
|
||||
items: [
|
||||
'components/bullet-actor',
|
||||
'components/barrel-component',
|
||||
'components/ballistic-impact-component',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Mathematical Ballistics',
|
||||
items: [
|
||||
'mathematical/overview',
|
||||
'mathematical/penetration-calculations',
|
||||
'mathematical/atmospheric-effects',
|
||||
'mathematical/drag-curves',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Networking',
|
||||
items: [
|
||||
'networking/overview',
|
||||
'networking/replication',
|
||||
'networking/prediction',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Performance',
|
||||
items: [
|
||||
'performance/optimization',
|
||||
'performance/pooling',
|
||||
'performance/profiling',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorials',
|
||||
items: [
|
||||
'tutorials/first-weapon',
|
||||
'tutorials/material-setup',
|
||||
'tutorials/advanced-ballistics',
|
||||
'tutorials/multiplayer-setup',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'API Reference',
|
||||
items: [
|
||||
'api/overview',
|
||||
'api/bullet-actor',
|
||||
'api/barrel-component',
|
||||
'api/impact-component',
|
||||
'api/mathematical-ballistics',
|
||||
'api/events',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Migration Guide',
|
||||
items: [
|
||||
'migration/from-legacy',
|
||||
'migration/new-impact-system',
|
||||
'migration/breaking-changes',
|
||||
],
|
||||
},
|
||||
'troubleshooting',
|
||||
'changelog',
|
||||
],
|
||||
};
|
||||
|
||||
export default sidebars;
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Any CSS included here will be global. The classic template
|
||||
* bundles Infima by default. Infima is a CSS framework designed to
|
||||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #2e8555;
|
||||
--ifm-color-primary-dark: #29784c;
|
||||
--ifm-color-primary-darker: #277148;
|
||||
--ifm-color-primary-darkest: #205d3b;
|
||||
--ifm-color-primary-light: #33925d;
|
||||
--ifm-color-primary-lighter: #359962;
|
||||
--ifm-color-primary-lightest: #3cad6e;
|
||||
--ifm-code-font-size: 95%;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-color-primary-dark: #21af90;
|
||||
--ifm-color-primary-darker: #1fa588;
|
||||
--ifm-color-primary-darkest: #1a8870;
|
||||
--ifm-color-primary-light: #29d5b0;
|
||||
--ifm-color-primary-lighter: #32d8b4;
|
||||
--ifm-color-primary-lightest: #4fddbf;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Custom styles for EasyBallistics */
|
||||
.hero__title {
|
||||
background: linear-gradient(45deg, #ff6b35, #f7931e);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.ballistics-card {
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
background: var(--ifm-background-surface-color);
|
||||
}
|
||||
|
||||
.ballistics-card h3 {
|
||||
color: var(--ifm-color-primary);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.code-block-highlight {
|
||||
background: var(--ifm-color-warning-contrast-background);
|
||||
border-left: 4px solid var(--ifm-color-warning);
|
||||
padding: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.api-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.api-table th,
|
||||
.api-table td {
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.api-table th {
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: var(--ifm-color-warning-contrast-background);
|
||||
border: 1px solid var(--ifm-color-warning);
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: var(--ifm-color-info-contrast-background);
|
||||
border: 1px solid var(--ifm-color-info);
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.success-box {
|
||||
background: var(--ifm-color-success-contrast-background);
|
||||
border: 1px solid var(--ifm-color-success);
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
Reference in New Issue
Block a user