From e2e709bffdb04edb0e0cd350ff5432d7cf9a5766 Mon Sep 17 00:00:00 2001 From: aicorr Date: Tue, 15 Apr 2025 22:21:28 +0530 Subject: [PATCH] Implement enemy shooting and scale down for more arcade game feel --- Content/Blueprints/BP_EnemyProjectile.uasset | 3 + Content/Blueprints/BP_EnemySpaceship.uasset | 4 +- Content/Blueprints/BP_SpaceshipPawn.uasset | 4 +- .../Blueprints/BP_SpaceshipProjectile.uasset | 2 +- Content/Main.umap | 2 +- Source/MyProject3/EnemyProjectile.cpp | 156 ++++++++++ Source/MyProject3/EnemyProjectile.h | 43 +++ Source/MyProject3/EnemySpaceship.cpp | 277 +++++++++++++++++- Source/MyProject3/EnemySpaceship.h | 77 ++++- Source/MyProject3/SpaceShooterGameMode.cpp | 66 ++++- Source/MyProject3/SpaceShooterGameMode.h | 8 +- Source/MyProject3/SpaceshipPawn.cpp | 203 ++++++++++++- Source/MyProject3/SpaceshipPawn.h | 80 ++++- Source/MyProject3/SpaceshipProjectile.h | 2 +- 14 files changed, 886 insertions(+), 41 deletions(-) create mode 100644 Content/Blueprints/BP_EnemyProjectile.uasset create mode 100644 Source/MyProject3/EnemyProjectile.cpp create mode 100644 Source/MyProject3/EnemyProjectile.h diff --git a/Content/Blueprints/BP_EnemyProjectile.uasset b/Content/Blueprints/BP_EnemyProjectile.uasset new file mode 100644 index 0000000..ff2d02f --- /dev/null +++ b/Content/Blueprints/BP_EnemyProjectile.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6e15734c3b687fdb400d024ca5bbfdb21c47f548bbd3c57b582680f029ade3e +size 30763 diff --git a/Content/Blueprints/BP_EnemySpaceship.uasset b/Content/Blueprints/BP_EnemySpaceship.uasset index 29a4df5..ad0ae9e 100644 --- a/Content/Blueprints/BP_EnemySpaceship.uasset +++ b/Content/Blueprints/BP_EnemySpaceship.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4e26c26896c7225386a0b925bab12069393cc61765f9a32422cd0bac048769c -size 31068 +oid sha256:8dc909a51aa86f1721b8bee661ac1823a38bdef7544a11dae37982de892ab929 +size 30916 diff --git a/Content/Blueprints/BP_SpaceshipPawn.uasset b/Content/Blueprints/BP_SpaceshipPawn.uasset index 1e016f0..94d849b 100644 --- a/Content/Blueprints/BP_SpaceshipPawn.uasset +++ b/Content/Blueprints/BP_SpaceshipPawn.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:893e409859f0f7be4ae3671727ed2c0a6f69187d59f6273f281059d39d6147b7 -size 34023 +oid sha256:36c53041140c765d2c91438f7219c8004e61d61ab1c49335b870999232792e6c +size 33621 diff --git a/Content/Blueprints/BP_SpaceshipProjectile.uasset b/Content/Blueprints/BP_SpaceshipProjectile.uasset index bd3549c..fa4f0f4 100644 --- a/Content/Blueprints/BP_SpaceshipProjectile.uasset +++ b/Content/Blueprints/BP_SpaceshipProjectile.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74207b1b33c44d5561faa52028420879c4b9aeb0071e5871d68a2e41903fa141 +oid sha256:7ddc8b567ebcb8d509a3b067ece867985461758f4eebe51b7f242f19e2849711 size 32752 diff --git a/Content/Main.umap b/Content/Main.umap index 23a8717..778cf95 100644 --- a/Content/Main.umap +++ b/Content/Main.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d9747ae79aa7a7c0c33443fea565a73bf3d5d7e3fdbfc80b1d5fd8144a59743 +oid sha256:408f5437756de7b851860ade1c3ec06befec1293dfd15577b486889fcf521de6 size 70812 diff --git a/Source/MyProject3/EnemyProjectile.cpp b/Source/MyProject3/EnemyProjectile.cpp new file mode 100644 index 0000000..6301f42 --- /dev/null +++ b/Source/MyProject3/EnemyProjectile.cpp @@ -0,0 +1,156 @@ +#include "EnemyProjectile.h" +#include "GameFramework/ProjectileMovementComponent.h" +#include "Components/StaticMeshComponent.h" +#include "SpaceshipPawn.h" // Include the player ship header +#include "Kismet/GameplayStatics.h" +#include "Engine/Engine.h" + +AEnemyProjectile::AEnemyProjectile() +{ + PrimaryActorTick.bCanEverTick = true; + + // Create and setup the projectile mesh + ProjectileMesh = CreateDefaultSubobject(TEXT("ProjectileMesh")); + RootComponent = ProjectileMesh; + ProjectileMesh->SetCollisionProfileName(TEXT("EnemyProjectile")); + + // Set up collision + ProjectileMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + ProjectileMesh->SetCollisionObjectType(ECC_WorldDynamic); + ProjectileMesh->SetCollisionResponseToAllChannels(ECR_Ignore); + + // Block GameTraceChannel1 (player ship) + ProjectileMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Block); + + // Block world static for environment collisions + ProjectileMesh->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block); + + // Block pawns for player ship collision + ProjectileMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block); + + // Enable overlap events + ProjectileMesh->SetGenerateOverlapEvents(true); + + // Bind hit event + ProjectileMesh->OnComponentHit.AddDynamic(this, &AEnemyProjectile::OnHit); + + // Bind overlap event for redundant collision detection + ProjectileMesh->OnComponentBeginOverlap.AddDynamic(this, &AEnemyProjectile::OnOverlapBegin); + + // Create and setup projectile movement + ProjectileMovement = CreateDefaultSubobject(TEXT("ProjectileMovement")); + ProjectileMovement->UpdatedComponent = ProjectileMesh; + ProjectileMovement->InitialSpeed = ProjectileSpeed; + ProjectileMovement->MaxSpeed = ProjectileSpeed; + ProjectileMovement->bRotationFollowsVelocity = true; + ProjectileMovement->ProjectileGravityScale = 0.0f; + + // Important: Enable sweep to prevent tunneling at high speeds + ProjectileMovement->bSweepCollision = true; + + // Set lifetime + InitialLifeSpan = 3.0f; +} + +void AEnemyProjectile::BeginPlay() +{ + Super::BeginPlay(); + + // Debug message to verify projectile spawned + if (GEngine) + { + GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Blue, TEXT("Enemy Projectile Spawned")); + } +} + +void AEnemyProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, + FVector NormalImpulse, const FHitResult& Hit) +{ + // Debug message to verify hit detection + if (GEngine && OtherActor) + { + GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Orange, + FString::Printf(TEXT("Enemy Projectile hit: %s"), *OtherActor->GetName())); + } + + if (OtherActor && OtherActor != this) + { + // Check if we hit a player ship + ASpaceshipPawn* Player = Cast(OtherActor); + if (Player) + { + // Apply damage to player + FDamageEvent DamageEvent; + float ActualDamage = Player->TakeDamage(DamageAmount, DamageEvent, nullptr, this); + + // Debug message for damage + if (GEngine) + { + GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, + FString::Printf(TEXT("Dealt %.1f damage to player"), ActualDamage)); + } + + // Spawn impact effect if set + if (ImpactEffect) + { + UGameplayStatics::SpawnEmitterAtLocation( + GetWorld(), + ImpactEffect, + Hit.Location, + Hit.Normal.Rotation() + ); + } + } + } + + // Destroy the projectile + Destroy(); +} + +void AEnemyProjectile::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, + bool bFromSweep, const FHitResult& SweepResult) +{ + // This is a redundant collision detection method + // Some fast-moving projectiles might miss hit events but catch overlap events + + // Debug message to verify overlap detection + if (GEngine && OtherActor) + { + GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Yellow, + FString::Printf(TEXT("Enemy Projectile overlap with: %s"), *OtherActor->GetName())); + } + + if (OtherActor && OtherActor != this) + { + // Check if we overlap with a player ship + ASpaceshipPawn* Player = Cast(OtherActor); + if (Player) + { + // Apply damage to player + FDamageEvent DamageEvent; + float ActualDamage = Player->TakeDamage(DamageAmount, DamageEvent, nullptr, this); + + // Debug message for damage + if (GEngine) + { + GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, + FString::Printf(TEXT("Overlap - Dealt %.1f damage to player"), ActualDamage)); + } + + // Spawn impact effect if set + if (ImpactEffect) + { + UGameplayStatics::SpawnEmitterAtLocation( + GetWorld(), + ImpactEffect, + GetActorLocation(), + GetActorRotation() + ); + } + + // Destroy the projectile + Destroy(); + } + } +} \ No newline at end of file diff --git a/Source/MyProject3/EnemyProjectile.h b/Source/MyProject3/EnemyProjectile.h new file mode 100644 index 0000000..123f113 --- /dev/null +++ b/Source/MyProject3/EnemyProjectile.h @@ -0,0 +1,43 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "EnemyProjectile.generated.h" + +UCLASS() +class MYPROJECT3_API AEnemyProjectile : public AActor +{ + GENERATED_BODY() + +public: + AEnemyProjectile(); + +protected: + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components") + class UStaticMeshComponent* ProjectileMesh; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components") + class UProjectileMovementComponent* ProjectileMovement; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile") + float ProjectileSpeed = 2500.0f; // Slightly slower than player projectiles + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile") + float DamageAmount = 10.0f; // Less damage than player projectiles + + // Add a particle effect for impact + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + class UParticleSystem* ImpactEffect; + + UFUNCTION() + void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, + FVector NormalImpulse, const FHitResult& Hit); + + // Add an overlap handler for redundant collision detection + UFUNCTION() + void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, + bool bFromSweep, const FHitResult& SweepResult); +}; \ No newline at end of file diff --git a/Source/MyProject3/EnemySpaceship.cpp b/Source/MyProject3/EnemySpaceship.cpp index 9e1056c..c95b13d 100644 --- a/Source/MyProject3/EnemySpaceship.cpp +++ b/Source/MyProject3/EnemySpaceship.cpp @@ -1,5 +1,14 @@ #include "EnemySpaceship.h" #include "Kismet/GameplayStatics.h" +#include "GameFramework/ProjectileMovementComponent.h" +#include "Components/SphereComponent.h" +#include "Engine/World.h" +#include "DrawDebugHelpers.h" +#include "Math/UnrealMathUtility.h" +#include "EnemyProjectile.h" + +// Include SpaceshipPawn to access player-specific functionality +#include "SpaceshipPawn.h" AEnemySpaceship::AEnemySpaceship() { @@ -9,6 +18,11 @@ AEnemySpaceship::AEnemySpaceship() EnemyMesh = CreateDefaultSubobject(TEXT("EnemyMesh")); RootComponent = EnemyMesh; + // Create projectile spawn point + ProjectileSpawnPoint = CreateDefaultSubobject(TEXT("ProjectileSpawnPoint")); + ProjectileSpawnPoint->SetupAttachment(EnemyMesh); + ProjectileSpawnPoint->SetRelativeLocation(FVector(100.0f, 0.0f, 0.0f)); // Forward of the ship + // Disable gravity and physics simulation EnemyMesh->SetSimulatePhysics(false); EnemyMesh->SetEnableGravity(false); @@ -18,7 +32,7 @@ AEnemySpaceship::AEnemySpaceship() EnemyMesh->SetCollisionObjectType(ECC_Pawn); EnemyMesh->SetCollisionResponseToAllChannels(ECR_Block); EnemyMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore); // Ignore other pawns - EnemyMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Ignore); // Ignore player + EnemyMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Block); // Block player (for damage) EnemyMesh->SetGenerateOverlapEvents(true); } @@ -26,27 +40,253 @@ void AEnemySpaceship::BeginPlay() { Super::BeginPlay(); - // Find the player pawn - PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); + // Find the player pawn and cast to SpaceshipPawn + AActor* FoundPlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); + PlayerPawn = Cast(FoundPlayerPawn); + + // Initialize behavior state timer + GetWorldTimerManager().SetTimer(BehaviorTimerHandle, this, &AEnemySpaceship::ChangeBehaviorState, BehaviorChangeTime, true); + + // Initialize with the ability to fire + bCanFire = true; + + // Randomize initial strafe direction + StrafeDirection = FMath::RandBool() ? 1.0f : -1.0f; } void AEnemySpaceship::Tick(float DeltaTime) { Super::Tick(DeltaTime); + if (!PlayerPawn) + { + // Try to find player again if not set + AActor* FoundPlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); + PlayerPawn = Cast(FoundPlayerPawn); + + if (!PlayerPawn) + return; + } + + // Calculate distance to player + DistanceToPlayer = FVector::Dist(GetActorLocation(), PlayerPawn->GetActorLocation()); + + // Update behavior state based on distance + UpdateBehaviorState(); + + // Execute behavior based on current state + switch (CurrentBehaviorState) + { + case EEnemyBehaviorState::Chase: + PerformChase(DeltaTime); + break; + case EEnemyBehaviorState::Attack: + PerformAttack(DeltaTime); + break; + case EEnemyBehaviorState::Retreat: + PerformRetreat(DeltaTime); + break; + case EEnemyBehaviorState::Strafe: + PerformStrafe(DeltaTime); + break; + } + + // Debug state information + if (GEngine) + { + FString StateString; + switch (CurrentBehaviorState) + { + case EEnemyBehaviorState::Chase: StateString = "Chase"; break; + case EEnemyBehaviorState::Attack: StateString = "Attack"; break; + case EEnemyBehaviorState::Retreat: StateString = "Retreat"; break; + case EEnemyBehaviorState::Strafe: StateString = "Strafe"; break; + } + + GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Orange, + FString::Printf(TEXT("Enemy State: %s | Distance: %.1f"), *StateString, DistanceToPlayer)); + } +} + +void AEnemySpaceship::UpdateBehaviorState() +{ + // Override random behavior changes when distance requires specific behavior + if (DistanceToPlayer < MinDistanceToPlayer) + { + // Too close, retreat or strafe + CurrentBehaviorState = FMath::RandBool() ? EEnemyBehaviorState::Retreat : EEnemyBehaviorState::Strafe; + } + else if (DistanceToPlayer < AttackRange) + { + // Within attack range but not too close, either attack or strafe + if (CurrentBehaviorState != EEnemyBehaviorState::Attack && + CurrentBehaviorState != EEnemyBehaviorState::Strafe) + { + CurrentBehaviorState = FMath::RandBool() ? EEnemyBehaviorState::Attack : EEnemyBehaviorState::Strafe; + } + } + else + { + // Too far, chase + CurrentBehaviorState = EEnemyBehaviorState::Chase; + } +} + +void AEnemySpaceship::ChangeBehaviorState() +{ + // Random chance to change behavior state + if (FMath::FRand() < StateChangeChance) + { + // Pick a random state + int32 NewState = FMath::RandRange(0, 3); + CurrentBehaviorState = static_cast(NewState); + + // Randomize strafe direction when entering strafe mode + if (CurrentBehaviorState == EEnemyBehaviorState::Strafe) + { + StrafeDirection = FMath::RandBool() ? 1.0f : -1.0f; + } + } +} + +void AEnemySpaceship::PerformChase(float DeltaTime) +{ if (PlayerPawn) { - // Move towards the player + // Calculate direction to player FVector Direction = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal(); + + // Move towards the player FVector NewLocation = GetActorLocation() + Direction * MovementSpeed * DeltaTime; SetActorLocation(NewLocation); // Face towards the player FRotator NewRotation = Direction.Rotation(); SetActorRotation(NewRotation); + + // If within attack range, fire occasionally + if (DistanceToPlayer < AttackRange && bCanFire && FMath::FRand() < 0.3f) + { + Fire(); + } } } +void AEnemySpaceship::PerformAttack(float DeltaTime) +{ + if (PlayerPawn) + { + // Face towards the player but don't move forward + FVector Direction = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal(); + FRotator NewRotation = Direction.Rotation(); + SetActorRotation(NewRotation); + + // Fire if ready + if (bCanFire) + { + Fire(); + } + } +} + +void AEnemySpaceship::PerformRetreat(float DeltaTime) +{ + if (PlayerPawn) + { + // Move away from player + FVector Direction = (GetActorLocation() - PlayerPawn->GetActorLocation()).GetSafeNormal(); + FVector NewLocation = GetActorLocation() + Direction * MovementSpeed * DeltaTime; + SetActorLocation(NewLocation); + + // Keep facing the player while backing up + FVector FaceDirection = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal(); + FRotator NewRotation = FaceDirection.Rotation(); + SetActorRotation(NewRotation); + + // Fire while retreating + if (bCanFire) + { + Fire(); + } + } +} + +void AEnemySpaceship::PerformStrafe(float DeltaTime) +{ + if (PlayerPawn) + { + // Calculate direction to player + FVector DirectionToPlayer = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal(); + + // Calculate strafe direction (perpendicular to direction to player) + FVector StrafeVector = FVector::CrossProduct(DirectionToPlayer, FVector::UpVector) * StrafeDirection; + + // Move sideways while maintaining distance + FVector NewLocation = GetActorLocation() + StrafeVector * StrafeSpeed * DeltaTime; + SetActorLocation(NewLocation); + + // Face towards the player + FRotator NewRotation = DirectionToPlayer.Rotation(); + SetActorRotation(NewRotation); + + // Fire while strafing + if (bCanFire) + { + Fire(); + } + } +} + +void AEnemySpaceship::Fire() +{ + if (!ProjectileClass) + { + if (GEngine) + GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, TEXT("Enemy ProjectileClass not set!")); + return; + } + + UWorld* World = GetWorld(); + if (World) + { + FVector SpawnLocation = ProjectileSpawnPoint->GetComponentLocation(); + FRotator SpawnRotation = GetActorRotation(); + + // Add slight randomness to rotation for less perfect aim + float RandPitch = FMath::RandRange(-5.0f, 5.0f); + float RandYaw = FMath::RandRange(-5.0f, 5.0f); + SpawnRotation.Pitch += RandPitch; + SpawnRotation.Yaw += RandYaw; + + FActorSpawnParameters SpawnParams; + SpawnParams.Owner = this; + SpawnParams.Instigator = GetInstigator(); + + // Spawn the projectile using EnemyProjectile class + AEnemyProjectile* Projectile = World->SpawnActor( + ProjectileClass, + SpawnLocation, + SpawnRotation, + SpawnParams + ); + + if (Projectile) + { + if (GEngine) + GEngine->AddOnScreenDebugMessage(-1, 0.5f, FColor::Yellow, TEXT("Enemy Fired!")); + } + + // Start fire rate timer + bCanFire = false; + GetWorldTimerManager().SetTimer(FireTimerHandle, this, &AEnemySpaceship::ResetFire, FireRate, false); + } +} + +void AEnemySpaceship::ResetFire() +{ + bCanFire = true; +} + float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) { @@ -58,7 +298,18 @@ float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& Damage if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, - FString::Printf(TEXT("Enemy Health: %f"), CurrentHealth)); + FString::Printf(TEXT("Enemy Hit! Health: %f"), CurrentHealth)); + } + + // When damaged, prefer retreat or strafe behaviors temporarily + if (FMath::RandBool()) + { + CurrentBehaviorState = EEnemyBehaviorState::Retreat; + } + else + { + CurrentBehaviorState = EEnemyBehaviorState::Strafe; + StrafeDirection = FMath::RandBool() ? 1.0f : -1.0f; } if (CurrentHealth <= 0) @@ -71,7 +322,21 @@ float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& Damage void AEnemySpaceship::Die() { - // Add any death effects here + // Play explosion effect + UGameplayStatics::SpawnEmitterAtLocation( + GetWorld(), + nullptr, // Add your explosion effect here + GetActorLocation(), + FRotator::ZeroRotator, + FVector(2.0f) + ); + + // Play explosion sound + UGameplayStatics::PlaySoundAtLocation( + this, + nullptr, // Add your explosion sound here + GetActorLocation() + ); // Destroy the enemy Destroy(); diff --git a/Source/MyProject3/EnemySpaceship.h b/Source/MyProject3/EnemySpaceship.h index 758e518..cf96471 100644 --- a/Source/MyProject3/EnemySpaceship.h +++ b/Source/MyProject3/EnemySpaceship.h @@ -4,6 +4,16 @@ #include "GameFramework/Actor.h" #include "EnemySpaceship.generated.h" +// Define AI behavior states +UENUM(BlueprintType) +enum class EEnemyBehaviorState : uint8 +{ + Chase, // Actively pursue the player + Attack, // Stop and shoot at the player + Retreat, // Move away from player when too close + Strafe // Move sideways while attacking +}; + UCLASS() class MYPROJECT3_API AEnemySpaceship : public AActor { @@ -18,8 +28,21 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components") UStaticMeshComponent* EnemyMesh; + // Projectile spawn points + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + USceneComponent* ProjectileSpawnPoint; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") - float MovementSpeed = 300.0f; + float MovementSpeed = 500.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") + float AttackRange = 1500.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") + float MinDistanceToPlayer = 500.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") + float StrafeSpeed = 300.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") float MaxHealth = 100.0f; @@ -27,6 +50,30 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") float CurrentHealth = 100.0f; + // Projectile class to spawn + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + TSubclassOf ProjectileClass; + + // Firing properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + float FireRate = 1.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + float ProjectileSpeed = 2000.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + float ProjectileDamage = 10.0f; + + // AI behavior properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI") + EEnemyBehaviorState CurrentBehaviorState = EEnemyBehaviorState::Chase; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI") + float BehaviorChangeTime = 3.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI") + float StateChangeChance = 0.3f; + public: virtual void Tick(float DeltaTime) override; @@ -35,6 +82,32 @@ public: class AController* EventInstigator, AActor* DamageCauser) override; private: - AActor* PlayerPawn; + // Reference to player + class ASpaceshipPawn* PlayerPawn; + + // Distance to player + float DistanceToPlayer; + + // Fire control + FTimerHandle FireTimerHandle; + bool bCanFire = true; + + // Strafe direction (1 = right, -1 = left) + float StrafeDirection = 1.0f; + + // Timer for changing behavior + FTimerHandle BehaviorTimerHandle; + + // Helper functions + void Fire(); + void ResetFire(); + void UpdateBehaviorState(); + void ChangeBehaviorState(); void Die(); + + // AI behavior implementation functions + void PerformChase(float DeltaTime); + void PerformAttack(float DeltaTime); + void PerformRetreat(float DeltaTime); + void PerformStrafe(float DeltaTime); }; \ No newline at end of file diff --git a/Source/MyProject3/SpaceShooterGameMode.cpp b/Source/MyProject3/SpaceShooterGameMode.cpp index 88a71f1..cf6ccdd 100644 --- a/Source/MyProject3/SpaceShooterGameMode.cpp +++ b/Source/MyProject3/SpaceShooterGameMode.cpp @@ -156,15 +156,18 @@ void ASpaceShooterGameMode::SpawnEnemyWave() // Create a line of enemies perpendicular to the direction FVector2D PerpDirection(-EdgeDirection.Y, EdgeDirection.X); - // Spawn wave of enemies + // Spawn wave of enemies - increased distance from 2000 to MinimumSpawnDistance for (int32 i = 0; i < WaveSize; i++) { - FVector SpawnLocation; - SpawnLocation.X = PlayerLocation.X + (EdgeDirection.X * 2000.0f) + + FVector ProposedLocation; + ProposedLocation.X = PlayerLocation.X + (EdgeDirection.X * MinimumSpawnDistance) + (PerpDirection.X * (i - WaveSize / 2) * FormationSpacing); - SpawnLocation.Y = PlayerLocation.Y + (EdgeDirection.Y * 2000.0f) + + ProposedLocation.Y = PlayerLocation.Y + (EdgeDirection.Y * MinimumSpawnDistance) + (PerpDirection.Y * (i - WaveSize / 2) * FormationSpacing); - SpawnLocation.Z = PlayerLocation.Z; + ProposedLocation.Z = PlayerLocation.Z; + + // Ensure the spawn location is far enough from the player + FVector SpawnLocation = EnsureMinimumSpawnDistance(ProposedLocation, PlayerLocation); FRotator SpawnRotation = FRotator::ZeroRotator; FActorSpawnParameters SpawnParams; @@ -205,8 +208,8 @@ void ASpaceShooterGameMode::SpawnEnemyFormation() float ApproachAngle = FMath::RandRange(0.0f, 2.0f * PI); FVector2D ApproachDir(FMath::Cos(ApproachAngle), FMath::Sin(ApproachAngle)); - // Base spawn position far from player - FVector BaseSpawnPos = PlayerLocation + FVector(ApproachDir.X, ApproachDir.Y, 0) * 2500.0f; + // Base spawn position far from player - increased from 2500 to MinimumSpawnDistance + 500 + FVector BaseSpawnPos = PlayerLocation + FVector(ApproachDir.X, ApproachDir.Y, 0) * (MinimumSpawnDistance + 500.0f); // Create formation positions TArray FormationPositions; @@ -261,13 +264,16 @@ void ASpaceShooterGameMode::SpawnEnemyFormation() // Spawn enemies at formation positions for (const FVector& Position : FormationPositions) { + // Ensure the spawn location is far enough from the player + FVector SpawnLocation = EnsureMinimumSpawnDistance(Position, PlayerLocation); + FRotator SpawnRotation = FRotator::ZeroRotator; FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AEnemySpaceship* NewEnemy = World->SpawnActor( - EnemyClass, Position, SpawnRotation, SpawnParams); + EnemyClass, SpawnLocation, SpawnRotation, SpawnParams); if (NewEnemy) { @@ -307,7 +313,11 @@ void ASpaceShooterGameMode::SpawnEnemyFlanking() float OffsetAngle = Angle + FMath::RandRange(-0.3f, 0.3f); FVector2D OffsetDir(FMath::Cos(OffsetAngle), FMath::Sin(OffsetAngle)); - FVector SpawnLocation = PlayerLocation + FVector(OffsetDir.X, OffsetDir.Y, 0) * 2000.0f; + // Increased from 2000 to MinimumSpawnDistance + FVector ProposedLocation = PlayerLocation + FVector(OffsetDir.X, OffsetDir.Y, 0) * MinimumSpawnDistance; + + // Ensure the spawn location is far enough from the player + FVector SpawnLocation = EnsureMinimumSpawnDistance(ProposedLocation, PlayerLocation); FRotator SpawnRotation = FRotator::ZeroRotator; FActorSpawnParameters SpawnParams; @@ -339,30 +349,34 @@ FVector ASpaceShooterGameMode::GetScreenEdgeSpawnLocation() FVector SpawnLocation; float RandomPos; + // Increased margin to spawn farther from screen edges + float ExtendedMargin = ScreenSpawnMargin + 500.0f; + switch (Edge) { case 0: // Top edge RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X); - SpawnLocation = FVector(RandomPos, ScreenBounds[0].Y - ScreenSpawnMargin, PlayerLocation.Z); + SpawnLocation = FVector(RandomPos, ScreenBounds[0].Y - ExtendedMargin, PlayerLocation.Z); break; case 1: // Right edge RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y); - SpawnLocation = FVector(ScreenBounds[1].X + ScreenSpawnMargin, RandomPos, PlayerLocation.Z); + SpawnLocation = FVector(ScreenBounds[1].X + ExtendedMargin, RandomPos, PlayerLocation.Z); break; case 2: // Bottom edge RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X); - SpawnLocation = FVector(RandomPos, ScreenBounds[1].Y + ScreenSpawnMargin, PlayerLocation.Z); + SpawnLocation = FVector(RandomPos, ScreenBounds[1].Y + ExtendedMargin, PlayerLocation.Z); break; case 3: // Left edge RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y); - SpawnLocation = FVector(ScreenBounds[0].X - ScreenSpawnMargin, RandomPos, PlayerLocation.Z); + SpawnLocation = FVector(ScreenBounds[0].X - ExtendedMargin, RandomPos, PlayerLocation.Z); break; } - return SpawnLocation; + // Ensure the spawn location is far enough from the player + return EnsureMinimumSpawnDistance(SpawnLocation, PlayerLocation); } FVector ASpaceShooterGameMode::GetSpawnZoneLocation() @@ -395,6 +409,7 @@ FVector ASpaceShooterGameMode::GetSpawnZoneLocation() // Select a zone based on weight float RandomWeight = FMath::RandRange(0.0f, TotalWeight); float WeightSum = 0.0f; + FVector PlayerLocation = GetPlayerLocation(); for (const FSpawnZone& Zone : ActiveZones) { @@ -411,7 +426,10 @@ FVector ASpaceShooterGameMode::GetSpawnZoneLocation() 0.0f ); - return Zone.Location + SpawnOffset; + FVector ProposedLocation = Zone.Location + SpawnOffset; + + // Ensure the spawn location is far enough from the player + return EnsureMinimumSpawnDistance(ProposedLocation, PlayerLocation); } } @@ -504,4 +522,22 @@ void ASpaceShooterGameMode::RotateTowardsPlayer(AEnemySpaceship* Enemy, const FV // Set the enemy's rotation Enemy->SetActorRotation(NewRotation); +} + +// New function to ensure enemies spawn at least MinimumSpawnDistance away from player +FVector ASpaceShooterGameMode::EnsureMinimumSpawnDistance(const FVector& ProposedLocation, const FVector& PlayerLocation) +{ + FVector Direction = ProposedLocation - PlayerLocation; + Direction.Z = 0; // Keep on the same Z plane as the player + float CurrentDistance = Direction.Size(); + + // If already far enough, return the proposed location + if (CurrentDistance >= MinimumSpawnDistance) + { + return ProposedLocation; + } + + // Otherwise, extend the vector to meet the minimum distance + Direction.Normalize(); + return PlayerLocation + Direction * MinimumSpawnDistance; } \ No newline at end of file diff --git a/Source/MyProject3/SpaceShooterGameMode.h b/Source/MyProject3/SpaceShooterGameMode.h index cca9724..2d703f1 100644 --- a/Source/MyProject3/SpaceShooterGameMode.h +++ b/Source/MyProject3/SpaceShooterGameMode.h @@ -55,7 +55,10 @@ protected: int32 MaxEnemies = 10; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") - float ScreenSpawnMargin = 100.0f; + float ScreenSpawnMargin = 300.0f; // Increased from 100.0f + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") + float MinimumSpawnDistance = 4000.0f; // New property to ensure minimum spawn distance UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") TArray SpawnZones; @@ -93,4 +96,7 @@ private: FVector GetPlayerLocation(); TArray GetScreenBounds(); void RotateTowardsPlayer(AEnemySpaceship* Enemy, const FVector& PlayerLocation); + + // New helper method to ensure minimum spawn distance + FVector EnsureMinimumSpawnDistance(const FVector& ProposedLocation, const FVector& PlayerLocation); }; \ No newline at end of file diff --git a/Source/MyProject3/SpaceshipPawn.cpp b/Source/MyProject3/SpaceshipPawn.cpp index 26d775f..dff1f30 100644 --- a/Source/MyProject3/SpaceshipPawn.cpp +++ b/Source/MyProject3/SpaceshipPawn.cpp @@ -1,6 +1,7 @@ #include "SpaceshipPawn.h" #include "GameFramework/SpringArmComponent.h" #include "Camera/CameraComponent.h" +#include "Camera/CameraShakeBase.h" #include "Components/StaticMeshComponent.h" #include "EnhancedInputComponent.h" #include "EnhancedInputSubsystems.h" @@ -59,6 +60,7 @@ ASpaceshipPawn::ASpaceshipPawn() ShipMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block); ShipMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECollisionResponse::ECR_Ignore); ShipMesh->SetCollisionResponseToChannel(ECC_Pawn, ECollisionResponse::ECR_Ignore); + ShipMesh->SetGenerateOverlapEvents(true); } } @@ -104,6 +106,11 @@ void ASpaceshipPawn::BeginPlay() } } } + + // Initialize health and shield values + CurrentHealth = MaxHealth; + CurrentShield = MaxShield; + LastDamageTime = 0.0f; } void ASpaceshipPawn::Tick(float DeltaTime) @@ -201,7 +208,7 @@ void ASpaceshipPawn::UpdateArcadeMovement(float DeltaTime) // Calculate desired velocity (forward/back + strafe) FVector DesiredVelocity = (ForwardVector * CurrentThrottleInput * MaxSpeed) + - (RightVector * CurrentStrafeInput * (MaxSpeed * 0.7f)); // Strafe is slightly slower + (RightVector * CurrentStrafeInput * (MaxSpeed * 0.95f)); // Strafe is slightly slower // Smoothly interpolate current velocity to desired velocity if (!FMath::IsNearlyZero(CurrentThrottleInput) || !FMath::IsNearlyZero(CurrentStrafeInput)) @@ -394,4 +401,198 @@ void ASpaceshipPawn::Fire() void ASpaceshipPawn::ResetFire() { bCanFire = true; +} + +float ASpaceshipPawn::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, + AController* EventInstigator, AActor* DamageCauser) +{ + float DamageToApply = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser); + + // Update last damage time + LastDamageTime = GetWorld()->GetTimeSeconds(); + + // Stop shield recharge and clear timer + GetWorldTimerManager().ClearTimer(ShieldRechargeTimerHandle); + + // Apply damage to shield first + if (CurrentShield > 0.0f) + { + if (CurrentShield >= DamageToApply) + { + // Shield absorbs all damage + CurrentShield -= DamageToApply; + DamageToApply = 0.0f; + } + else + { + // Shield absorbs part of the damage + DamageToApply -= CurrentShield; + CurrentShield = 0.0f; + } + } + + // Apply remaining damage to health + CurrentHealth -= DamageToApply; + + // Debug message + if (GEngine) + { + GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, + FString::Printf(TEXT("Player Hit! Health: %.1f, Shield: %.1f"), + CurrentHealth, CurrentShield)); + } + + // Apply visual damage effects + ApplyDamageFlash(); + + // Play impact effect + if (ImpactEffect) + { + UGameplayStatics::SpawnEmitterAttached( + ImpactEffect, + ShipMesh, + NAME_None, + GetActorLocation(), + GetActorRotation(), + EAttachLocation::KeepWorldPosition + ); + } + + // Play impact sound + if (ImpactSound) + { + UGameplayStatics::PlaySoundAtLocation( + this, + ImpactSound, + GetActorLocation() + ); + } + + // Start shield recharge timer after delay + GetWorldTimerManager().SetTimer( + ShieldRechargeTimerHandle, + this, + &ASpaceshipPawn::StartShieldRecharge, + ShieldRechargeDelay, + false + ); + + // Check for death + if (CurrentHealth <= 0.0f) + { + Die(); + } + + return DamageToApply; +} + +void ASpaceshipPawn::ApplyDamageFlash() +{ + // Apply material flash effect to indicate damage + if (ShipMesh && ShipMesh->GetMaterial(0)) + { + UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(ShipMesh->GetMaterial(0), this); + if (DynamicMaterial) + { + // Assuming the material has a parameter named "FlashColor" + DynamicMaterial->SetVectorParameterValue("FlashColor", DamageFlashColor); + ShipMesh->SetMaterial(0, DynamicMaterial); + + // Set timer to reset the flash + GetWorldTimerManager().SetTimer( + DamageFlashTimerHandle, + this, + &ASpaceshipPawn::ResetDamageFlash, + DamageFlashDuration, + false + ); + } + } +} + +void ASpaceshipPawn::ResetDamageFlash() +{ + // Reset material flash effect + if (ShipMesh && ShipMesh->GetMaterial(0)) + { + UMaterialInstanceDynamic* DynamicMaterial = Cast(ShipMesh->GetMaterial(0)); + if (DynamicMaterial) + { + // Reset the flash color (assuming transparent black means no flash) + DynamicMaterial->SetVectorParameterValue("FlashColor", FLinearColor(0.0f, 0.0f, 0.0f, 0.0f)); + } + } +} + +void ASpaceshipPawn::StartShieldRecharge() +{ + // Start continuous shield recharge + GetWorldTimerManager().SetTimer( + ShieldRechargeTimerHandle, + this, + &ASpaceshipPawn::RechargeShield, + 0.1f, // update every 0.1 seconds + true // looping + ); +} + +void ASpaceshipPawn::RechargeShield() +{ + // Recharge shield gradually + if (CurrentShield < MaxShield) + { + CurrentShield = FMath::Min(CurrentShield + (ShieldRechargeRate * 0.1f), MaxShield); + } + else + { + // Shield is full, stop the timer + GetWorldTimerManager().ClearTimer(ShieldRechargeTimerHandle); + } +} + +void ASpaceshipPawn::Die() +{ + // Play death explosion effect + UGameplayStatics::SpawnEmitterAtLocation( + GetWorld(), + ImpactEffect, + GetActorLocation(), + GetActorRotation(), + FVector(3.0f) // Larger explosion for death + ); + + // Play death sound + if (ImpactSound) + { + UGameplayStatics::PlaySoundAtLocation( + this, + ImpactSound, + GetActorLocation(), + 2.0f // Louder for death + ); + } + + // Hide the ship mesh + if (ShipMesh) + { + ShipMesh->SetVisibility(false); + ShipMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + } + + // Disable input + APlayerController* PC = Cast(GetController()); + if (PC) + { + DisableInput(PC); + } + + // You could either restart the level after a delay or show a game over screen here + // For example: + // GetWorldTimerManager().SetTimer(RestartTimerHandle, this, &ASpaceshipPawn::RestartLevel, 3.0f, false); + + // For now, just log the death + if (GEngine) + { + GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("PLAYER DIED!")); + } } \ No newline at end of file diff --git a/Source/MyProject3/SpaceshipPawn.h b/Source/MyProject3/SpaceshipPawn.h index 49abce6..2074378 100644 --- a/Source/MyProject3/SpaceshipPawn.h +++ b/Source/MyProject3/SpaceshipPawn.h @@ -25,6 +25,18 @@ public: virtual void Tick(float DeltaTime) override; virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + // Override TakeDamage to handle player damage + virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, + class AController* EventInstigator, AActor* DamageCauser) override; + + // Get current health percentage + UFUNCTION(BlueprintCallable, Category = "Combat") + float GetHealthPercentage() const { return CurrentHealth / MaxHealth; } + + // Get current shield percentage + UFUNCTION(BlueprintCallable, Category = "Combat") + float GetShieldPercentage() const { return CurrentShield / MaxShield; } + protected: virtual void BeginPlay() override; @@ -48,12 +60,20 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input") class UInputAction* MouseLookAction; + // Add a strafe input to allow lateral movement + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input") + class UInputAction* StrafeAction; + + // Input action for shooting + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input") + class UInputAction* ShootAction; + // Movement Parameters UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") EShipMovementMode MovementMode = EShipMovementMode::Arcade; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") - float MaxSpeed = 2000.0f; + float MaxSpeed = 3000.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") float Acceleration = 2000.0f; @@ -86,10 +106,6 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") float AutoBrakeStrength = 3.0f; - // Add a strafe input to allow lateral movement - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input") - class UInputAction* StrafeAction; - // Shooting properties UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") TSubclassOf ProjectileClass; @@ -100,10 +116,6 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") USceneComponent* ProjectileSpawnPoint; - // Input action for shooting - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input") - class UInputAction* ShootAction; - UPROPERTY(EditDefaultsOnly, Category = "UI") TSubclassOf CrosshairWidgetClass; @@ -115,6 +127,43 @@ protected: void HandleStrafeInput(const FInputActionValue& Value); void HandleMouseLook(const FInputActionValue& Value); + // Health properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + float MaxHealth = 100.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + float CurrentHealth = 100.0f; + + // Shield properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + float MaxShield = 100.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + float CurrentShield = 100.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + float ShieldRechargeRate = 5.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") + float ShieldRechargeDelay = 3.0f; + + // Impact effects + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + UParticleSystem* ImpactEffect; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + class USoundBase* ImpactSound; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + class UCameraShakeBase* DamageShake; + + // Damage flash effect for the ship + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + float DamageFlashDuration = 0.2f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + FLinearColor DamageFlashColor = FLinearColor(1.0f, 0.0f, 0.0f, 0.5f); + private: // Movement state float CurrentThrottleInput; @@ -143,4 +192,17 @@ private: void UpdateAssistedMovement(float DeltaTime); void UpdateRealisticMovement(float DeltaTime); void UpdateShipRotation(float DeltaTime); + + // Damage handling + void ApplyDamageFlash(); + void ResetDamageFlash(); + + FTimerHandle ShieldRechargeTimerHandle; + FTimerHandle DamageFlashTimerHandle; + float LastDamageTime; + + void StartShieldRecharge(); + void RechargeShield(); + bool IsDead() const { return CurrentHealth <= 0.0f; } + void Die(); }; \ No newline at end of file diff --git a/Source/MyProject3/SpaceshipProjectile.h b/Source/MyProject3/SpaceshipProjectile.h index 3c6bd95..5659051 100644 --- a/Source/MyProject3/SpaceshipProjectile.h +++ b/Source/MyProject3/SpaceshipProjectile.h @@ -22,7 +22,7 @@ protected: class UProjectileMovementComponent* ProjectileMovement; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile") - float ProjectileSpeed = 3000.0f; + float ProjectileSpeed = 3500.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile") float DamageAmount = 20.0f;