2 Commits

14 changed files with 886 additions and 41 deletions

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,27 +40,253 @@ 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,
AController* EventInstigator, AActor* DamageCauser) AController* EventInstigator, AActor* DamageCauser)
{ {
@@ -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;