#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() { PrimaryActorTick.bCanEverTick = true; // Create and setup the enemy mesh 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); // Set up collision for the enemy mesh EnemyMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); EnemyMesh->SetCollisionObjectType(ECC_Pawn); EnemyMesh->SetCollisionResponseToAllChannels(ECR_Block); EnemyMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore); // Ignore other pawns EnemyMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Block); // Block player (for damage) EnemyMesh->SetGenerateOverlapEvents(true); } void AEnemySpaceship::BeginPlay() { Super::BeginPlay(); // 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) { // 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) { float DamageToApply = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser); CurrentHealth -= DamageToApply; // Debug message if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, 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) { Die(); } return DamageToApply; } void AEnemySpaceship::Die() { // 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(); }