7 Commits

Author SHA1 Message Date
bc03fd5091 Merge branch 'main' into EnvironmentDevelopment 2025-04-15 22:32:58 +05:30
e2e709bffd Implement enemy shooting and scale down for more arcade game feel 2025-04-15 22:21:28 +05:30
Shashank
39ace4311d Optimized Environment for Improved Performance 2025-04-15 22:21:02 +05:30
Shashank
e2c4db236f Replaced incorrect file with proper version 2025-04-15 21:38:08 +05:30
Shashank
22aea1380f Modified RingField 2025-04-15 20:04:19 +05:30
Shashank
08b6dd45fa Modified AsteriodBelts and Added InitialSpawnLocation 2025-04-15 19:03:33 +05:30
Shashank
d0a4f6effc Modified Ring's Material and Asteriod Ring 2025-04-15 16:12:20 +05:30
21 changed files with 903 additions and 49 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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<UStaticMeshComponent>(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<UProjectileMovementComponent>(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<ASpaceshipPawn>(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<ASpaceshipPawn>(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();
}
}
}

View File

@@ -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);
};

View File

@@ -1,5 +1,14 @@
#include "EnemySpaceship.h" #include "EnemySpaceship.h"
#include "Kismet/GameplayStatics.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() AEnemySpaceship::AEnemySpaceship()
{ {
@@ -9,6 +18,11 @@ AEnemySpaceship::AEnemySpaceship()
EnemyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EnemyMesh")); EnemyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EnemyMesh"));
RootComponent = EnemyMesh; RootComponent = EnemyMesh;
// Create projectile spawn point
ProjectileSpawnPoint = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSpawnPoint"));
ProjectileSpawnPoint->SetupAttachment(EnemyMesh);
ProjectileSpawnPoint->SetRelativeLocation(FVector(100.0f, 0.0f, 0.0f)); // Forward of the ship
// Disable gravity and physics simulation // Disable gravity and physics simulation
EnemyMesh->SetSimulatePhysics(false); EnemyMesh->SetSimulatePhysics(false);
EnemyMesh->SetEnableGravity(false); EnemyMesh->SetEnableGravity(false);
@@ -18,7 +32,7 @@ AEnemySpaceship::AEnemySpaceship()
EnemyMesh->SetCollisionObjectType(ECC_Pawn); EnemyMesh->SetCollisionObjectType(ECC_Pawn);
EnemyMesh->SetCollisionResponseToAllChannels(ECR_Block); EnemyMesh->SetCollisionResponseToAllChannels(ECR_Block);
EnemyMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore); // Ignore other pawns 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); EnemyMesh->SetGenerateOverlapEvents(true);
} }
@@ -26,25 +40,251 @@ void AEnemySpaceship::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
// Find the player pawn // Find the player pawn and cast to SpaceshipPawn
PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); AActor* FoundPlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
PlayerPawn = Cast<ASpaceshipPawn>(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) void AEnemySpaceship::Tick(float DeltaTime)
{ {
Super::Tick(DeltaTime); Super::Tick(DeltaTime);
if (!PlayerPawn)
{
// Try to find player again if not set
AActor* FoundPlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
PlayerPawn = Cast<ASpaceshipPawn>(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<EEnemyBehaviorState>(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) if (PlayerPawn)
{ {
// Move towards the player // Calculate direction to player
FVector Direction = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal(); FVector Direction = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal();
// Move towards the player
FVector NewLocation = GetActorLocation() + Direction * MovementSpeed * DeltaTime; FVector NewLocation = GetActorLocation() + Direction * MovementSpeed * DeltaTime;
SetActorLocation(NewLocation); SetActorLocation(NewLocation);
// Face towards the player // Face towards the player
FRotator NewRotation = Direction.Rotation(); FRotator NewRotation = Direction.Rotation();
SetActorRotation(NewRotation); 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<AEnemyProjectile>(
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, float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
@@ -58,7 +298,18 @@ float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& Damage
if (GEngine) if (GEngine)
{ {
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, 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) if (CurrentHealth <= 0)
@@ -71,7 +322,21 @@ float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& Damage
void AEnemySpaceship::Die() 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 the enemy
Destroy(); Destroy();

View File

@@ -4,6 +4,16 @@
#include "GameFramework/Actor.h" #include "GameFramework/Actor.h"
#include "EnemySpaceship.generated.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() UCLASS()
class MYPROJECT3_API AEnemySpaceship : public AActor class MYPROJECT3_API AEnemySpaceship : public AActor
{ {
@@ -18,8 +28,21 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components")
UStaticMeshComponent* EnemyMesh; UStaticMeshComponent* EnemyMesh;
// Projectile spawn points
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
USceneComponent* ProjectileSpawnPoint;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") 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") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
float MaxHealth = 100.0f; float MaxHealth = 100.0f;
@@ -27,6 +50,30 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
float CurrentHealth = 100.0f; float CurrentHealth = 100.0f;
// Projectile class to spawn
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
TSubclassOf<class AEnemyProjectile> 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: public:
virtual void Tick(float DeltaTime) override; virtual void Tick(float DeltaTime) override;
@@ -35,6 +82,32 @@ public:
class AController* EventInstigator, AActor* DamageCauser) override; class AController* EventInstigator, AActor* DamageCauser) override;
private: 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(); void Die();
// AI behavior implementation functions
void PerformChase(float DeltaTime);
void PerformAttack(float DeltaTime);
void PerformRetreat(float DeltaTime);
void PerformStrafe(float DeltaTime);
}; };

View File

@@ -156,15 +156,18 @@ void ASpaceShooterGameMode::SpawnEnemyWave()
// Create a line of enemies perpendicular to the direction // Create a line of enemies perpendicular to the direction
FVector2D PerpDirection(-EdgeDirection.Y, EdgeDirection.X); 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++) for (int32 i = 0; i < WaveSize; i++)
{ {
FVector SpawnLocation; FVector ProposedLocation;
SpawnLocation.X = PlayerLocation.X + (EdgeDirection.X * 2000.0f) + ProposedLocation.X = PlayerLocation.X + (EdgeDirection.X * MinimumSpawnDistance) +
(PerpDirection.X * (i - WaveSize / 2) * FormationSpacing); (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); (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; FRotator SpawnRotation = FRotator::ZeroRotator;
FActorSpawnParameters SpawnParams; FActorSpawnParameters SpawnParams;
@@ -205,8 +208,8 @@ void ASpaceShooterGameMode::SpawnEnemyFormation()
float ApproachAngle = FMath::RandRange(0.0f, 2.0f * PI); float ApproachAngle = FMath::RandRange(0.0f, 2.0f * PI);
FVector2D ApproachDir(FMath::Cos(ApproachAngle), FMath::Sin(ApproachAngle)); FVector2D ApproachDir(FMath::Cos(ApproachAngle), FMath::Sin(ApproachAngle));
// Base spawn position far from player // Base spawn position far from player - increased from 2500 to MinimumSpawnDistance + 500
FVector BaseSpawnPos = PlayerLocation + FVector(ApproachDir.X, ApproachDir.Y, 0) * 2500.0f; FVector BaseSpawnPos = PlayerLocation + FVector(ApproachDir.X, ApproachDir.Y, 0) * (MinimumSpawnDistance + 500.0f);
// Create formation positions // Create formation positions
TArray<FVector> FormationPositions; TArray<FVector> FormationPositions;
@@ -261,13 +264,16 @@ void ASpaceShooterGameMode::SpawnEnemyFormation()
// Spawn enemies at formation positions // Spawn enemies at formation positions
for (const FVector& Position : FormationPositions) for (const FVector& Position : FormationPositions)
{ {
// Ensure the spawn location is far enough from the player
FVector SpawnLocation = EnsureMinimumSpawnDistance(Position, PlayerLocation);
FRotator SpawnRotation = FRotator::ZeroRotator; FRotator SpawnRotation = FRotator::ZeroRotator;
FActorSpawnParameters SpawnParams; FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = SpawnParams.SpawnCollisionHandlingOverride =
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
AEnemySpaceship* NewEnemy = World->SpawnActor<AEnemySpaceship>( AEnemySpaceship* NewEnemy = World->SpawnActor<AEnemySpaceship>(
EnemyClass, Position, SpawnRotation, SpawnParams); EnemyClass, SpawnLocation, SpawnRotation, SpawnParams);
if (NewEnemy) if (NewEnemy)
{ {
@@ -307,7 +313,11 @@ void ASpaceShooterGameMode::SpawnEnemyFlanking()
float OffsetAngle = Angle + FMath::RandRange(-0.3f, 0.3f); float OffsetAngle = Angle + FMath::RandRange(-0.3f, 0.3f);
FVector2D OffsetDir(FMath::Cos(OffsetAngle), FMath::Sin(OffsetAngle)); 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; FRotator SpawnRotation = FRotator::ZeroRotator;
FActorSpawnParameters SpawnParams; FActorSpawnParameters SpawnParams;
@@ -339,30 +349,34 @@ FVector ASpaceShooterGameMode::GetScreenEdgeSpawnLocation()
FVector SpawnLocation; FVector SpawnLocation;
float RandomPos; float RandomPos;
// Increased margin to spawn farther from screen edges
float ExtendedMargin = ScreenSpawnMargin + 500.0f;
switch (Edge) switch (Edge)
{ {
case 0: // Top edge case 0: // Top edge
RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X); 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; break;
case 1: // Right edge case 1: // Right edge
RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y); 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; break;
case 2: // Bottom edge case 2: // Bottom edge
RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X); 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; break;
case 3: // Left edge case 3: // Left edge
RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y); 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; break;
} }
return SpawnLocation; // Ensure the spawn location is far enough from the player
return EnsureMinimumSpawnDistance(SpawnLocation, PlayerLocation);
} }
FVector ASpaceShooterGameMode::GetSpawnZoneLocation() FVector ASpaceShooterGameMode::GetSpawnZoneLocation()
@@ -395,6 +409,7 @@ FVector ASpaceShooterGameMode::GetSpawnZoneLocation()
// Select a zone based on weight // Select a zone based on weight
float RandomWeight = FMath::RandRange(0.0f, TotalWeight); float RandomWeight = FMath::RandRange(0.0f, TotalWeight);
float WeightSum = 0.0f; float WeightSum = 0.0f;
FVector PlayerLocation = GetPlayerLocation();
for (const FSpawnZone& Zone : ActiveZones) for (const FSpawnZone& Zone : ActiveZones)
{ {
@@ -411,7 +426,10 @@ FVector ASpaceShooterGameMode::GetSpawnZoneLocation()
0.0f 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);
} }
} }
@@ -505,3 +523,21 @@ void ASpaceShooterGameMode::RotateTowardsPlayer(AEnemySpaceship* Enemy, const FV
// Set the enemy's rotation // Set the enemy's rotation
Enemy->SetActorRotation(NewRotation); 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;
}

View File

@@ -55,7 +55,10 @@ protected:
int32 MaxEnemies = 10; int32 MaxEnemies = 10;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") 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") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
TArray<FSpawnZone> SpawnZones; TArray<FSpawnZone> SpawnZones;
@@ -93,4 +96,7 @@ private:
FVector GetPlayerLocation(); FVector GetPlayerLocation();
TArray<FVector2D> GetScreenBounds(); TArray<FVector2D> GetScreenBounds();
void RotateTowardsPlayer(AEnemySpaceship* Enemy, const FVector& PlayerLocation); void RotateTowardsPlayer(AEnemySpaceship* Enemy, const FVector& PlayerLocation);
// New helper method to ensure minimum spawn distance
FVector EnsureMinimumSpawnDistance(const FVector& ProposedLocation, const FVector& PlayerLocation);
}; };

View File

@@ -1,6 +1,7 @@
#include "SpaceshipPawn.h" #include "SpaceshipPawn.h"
#include "GameFramework/SpringArmComponent.h" #include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h" #include "Camera/CameraComponent.h"
#include "Camera/CameraShakeBase.h"
#include "Components/StaticMeshComponent.h" #include "Components/StaticMeshComponent.h"
#include "EnhancedInputComponent.h" #include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h" #include "EnhancedInputSubsystems.h"
@@ -59,6 +60,7 @@ ASpaceshipPawn::ASpaceshipPawn()
ShipMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block); ShipMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
ShipMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECollisionResponse::ECR_Ignore); ShipMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECollisionResponse::ECR_Ignore);
ShipMesh->SetCollisionResponseToChannel(ECC_Pawn, 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) void ASpaceshipPawn::Tick(float DeltaTime)
@@ -201,7 +208,7 @@ void ASpaceshipPawn::UpdateArcadeMovement(float DeltaTime)
// Calculate desired velocity (forward/back + strafe) // Calculate desired velocity (forward/back + strafe)
FVector DesiredVelocity = (ForwardVector * CurrentThrottleInput * MaxSpeed) + 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 // Smoothly interpolate current velocity to desired velocity
if (!FMath::IsNearlyZero(CurrentThrottleInput) || !FMath::IsNearlyZero(CurrentStrafeInput)) if (!FMath::IsNearlyZero(CurrentThrottleInput) || !FMath::IsNearlyZero(CurrentStrafeInput))
@@ -395,3 +402,197 @@ void ASpaceshipPawn::ResetFire()
{ {
bCanFire = true; 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<UMaterialInstanceDynamic>(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<APlayerController>(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!"));
}
}

View File

@@ -25,6 +25,18 @@ public:
virtual void Tick(float DeltaTime) override; virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) 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: protected:
virtual void BeginPlay() override; virtual void BeginPlay() override;
@@ -48,12 +60,20 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* MouseLookAction; 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 // Movement Parameters
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
EShipMovementMode MovementMode = EShipMovementMode::Arcade; EShipMovementMode MovementMode = EShipMovementMode::Arcade;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float MaxSpeed = 2000.0f; float MaxSpeed = 3000.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float Acceleration = 2000.0f; float Acceleration = 2000.0f;
@@ -86,10 +106,6 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float AutoBrakeStrength = 3.0f; float AutoBrakeStrength = 3.0f;
// Add a strafe input to allow lateral movement
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* StrafeAction;
// Shooting properties // Shooting properties
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
TSubclassOf<class ASpaceshipProjectile> ProjectileClass; TSubclassOf<class ASpaceshipProjectile> ProjectileClass;
@@ -100,10 +116,6 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
USceneComponent* ProjectileSpawnPoint; USceneComponent* ProjectileSpawnPoint;
// Input action for shooting
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* ShootAction;
UPROPERTY(EditDefaultsOnly, Category = "UI") UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<UUserWidget> CrosshairWidgetClass; TSubclassOf<UUserWidget> CrosshairWidgetClass;
@@ -115,6 +127,43 @@ protected:
void HandleStrafeInput(const FInputActionValue& Value); void HandleStrafeInput(const FInputActionValue& Value);
void HandleMouseLook(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: private:
// Movement state // Movement state
float CurrentThrottleInput; float CurrentThrottleInput;
@@ -143,4 +192,17 @@ private:
void UpdateAssistedMovement(float DeltaTime); void UpdateAssistedMovement(float DeltaTime);
void UpdateRealisticMovement(float DeltaTime); void UpdateRealisticMovement(float DeltaTime);
void UpdateShipRotation(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();
}; };

View File

@@ -22,7 +22,7 @@ protected:
class UProjectileMovementComponent* ProjectileMovement; class UProjectileMovementComponent* ProjectileMovement;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
float ProjectileSpeed = 3000.0f; float ProjectileSpeed = 3500.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
float DamageAmount = 20.0f; float DamageAmount = 20.0f;