#include "SpaceShooterGameMode.h" #include "SpaceshipPawn.h" #include "EnemySpaceship.h" #include "Kismet/GameplayStatics.h" #include "Engine/World.h" #include "GameFramework/PlayerController.h" ASpaceShooterGameMode::ASpaceShooterGameMode() { // Set default pawn class using the inherited DefaultPawnClass static ConstructorHelpers::FClassFinder PlayerPawnBPClass(TEXT("/Game/Blueprints/BP_SpaceshipPawn")); if (PlayerPawnBPClass.Class != nullptr) { DefaultPawnClass = PlayerPawnBPClass.Class; } // Find enemy blueprint class static ConstructorHelpers::FClassFinder EnemyBPClass(TEXT("/Game/Blueprints/BP_EnemySpaceship")); if (EnemyBPClass.Class != nullptr) { EnemyClass = EnemyBPClass.Class; } // Enable Tick() PrimaryActorTick.bCanEverTick = true; // Initialize spawn patterns SpawnPatterns.Add(ESpawnPattern::Random); SpawnPatterns.Add(ESpawnPattern::Wave); SpawnPatterns.Add(ESpawnPattern::Formation); SpawnPatterns.Add(ESpawnPattern::Flanking); // Set default values CurrentSpawnInterval = BaseEnemySpawnInterval; CurrentPattern = ESpawnPattern::Random; CurrentWaveCount = 0; GameTime = 0.0f; // Debug message if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("GameMode Constructor")); } KillCount = 0; bIsGameOver = false; RemainingTime = GameDuration; } void ASpaceShooterGameMode::StartPlay() { Super::StartPlay(); // Start spawning enemies GetWorldTimerManager().SetTimer(EnemySpawnTimer, this, &ASpaceShooterGameMode::SpawnEnemy, CurrentSpawnInterval, true); // Start difficulty scaling GetWorldTimerManager().SetTimer(DifficultyTimer, this, &ASpaceShooterGameMode::UpdateDifficulty, DifficultyInterval, true); // Debug message if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("GameMode StartPlay")); } RemainingTime = GameDuration; GetWorldTimerManager().SetTimer(GameTimerHandle, this, &ASpaceShooterGameMode::UpdateGameTimer, 1.0f, true); } void ASpaceShooterGameMode::Tick(float DeltaTime) { Super::Tick(DeltaTime); GameTime += DeltaTime; // Potentially change spawn pattern based on game time if (FMath::RandRange(0.0f, 1.0f) < 0.001f) { CurrentPattern = SpawnPatterns[FMath::RandRange(0, SpawnPatterns.Num() - 1)]; if (GEngine) { FString PatternName; switch (CurrentPattern) { case ESpawnPattern::Random: PatternName = TEXT("Random"); break; case ESpawnPattern::Wave: PatternName = TEXT("Wave"); break; case ESpawnPattern::Formation: PatternName = TEXT("Formation"); break; case ESpawnPattern::Flanking: PatternName = TEXT("Flanking"); break; } GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Cyan, FString::Printf(TEXT("Spawn Pattern Changed: %s"), *PatternName)); } } } void ASpaceShooterGameMode::SpawnEnemy() { // Count current enemies TArray FoundEnemies; UGameplayStatics::GetAllActorsOfClass(GetWorld(), AEnemySpaceship::StaticClass(), FoundEnemies); // Only spawn if we haven't reached the maximum if (FoundEnemies.Num() < MaxEnemies) { switch (CurrentPattern) { case ESpawnPattern::Random: { UWorld* World = GetWorld(); if (World && EnemyClass) { FVector SpawnLocation = GetRandomSpawnLocation(); FRotator SpawnRotation = FRotator::ZeroRotator; FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AEnemySpaceship* NewEnemy = World->SpawnActor( EnemyClass, SpawnLocation, SpawnRotation, SpawnParams); if (NewEnemy) { RotateTowardsPlayer(NewEnemy, GetPlayerLocation()); } } } break; case ESpawnPattern::Wave: SpawnEnemyWave(); break; case ESpawnPattern::Formation: SpawnEnemyFormation(); break; case ESpawnPattern::Flanking: SpawnEnemyFlanking(); break; } } } void ASpaceShooterGameMode::SpawnEnemyWave() { UWorld* World = GetWorld(); if (!World || !EnemyClass) return; // Get player location for facing direction FVector PlayerLocation = GetPlayerLocation(); // Choose a random angle for the wave float WaveAngle = FMath::RandRange(0.0f, 2.0f * PI); FVector2D BaseDirection(FMath::Cos(WaveAngle), FMath::Sin(WaveAngle)); // Create a perpendicular direction for the wave line FVector2D PerpDirection(-BaseDirection.Y, BaseDirection.X); // Spawn wave of enemies for (int32 i = 0; i < WaveSize; i++) { // Calculate base spawn position FVector BaseSpawnPos = PlayerLocation + FVector(BaseDirection.X, BaseDirection.Y, 0) * MinimumSpawnDistance; // Offset along the wave line FVector Offset = FVector(PerpDirection.X, PerpDirection.Y, 0) * (i - WaveSize / 2) * FormationSpacing; FVector SpawnLocation = BaseSpawnPos + Offset; FRotator SpawnRotation = FRotator::ZeroRotator; FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AEnemySpaceship* NewEnemy = World->SpawnActor( EnemyClass, SpawnLocation, SpawnRotation, SpawnParams); if (NewEnemy) { RotateTowardsPlayer(NewEnemy, PlayerLocation); } } CurrentWaveCount++; if (CurrentWaveCount >= 3) { CurrentPattern = ESpawnPattern::Random; CurrentWaveCount = 0; } } void ASpaceShooterGameMode::SpawnEnemyFormation() { // Count current enemies TArray FoundEnemies; UGameplayStatics::GetAllActorsOfClass(GetWorld(), AEnemySpaceship::StaticClass(), FoundEnemies); // Only spawn if we haven't reached the maximum if (FoundEnemies.Num() < MaxEnemies) { UWorld* World = GetWorld(); if (!World || !EnemyClass) return; // Get player location FVector PlayerLocation = GetPlayerLocation(); // Select a formation type (0 = V-formation, 1 = Line, 2 = Diamond) int32 FormationType = FMath::RandRange(0, 2); // Choose a random approach angle float ApproachAngle = FMath::RandRange(0.0f, 2.0f * PI); FVector2D ApproachDir(FMath::Cos(ApproachAngle), FMath::Sin(ApproachAngle)); // Base spawn position far from player - increased from 2500 to MinimumSpawnDistance + 500 FVector BaseSpawnPos = PlayerLocation + FVector(ApproachDir.X, ApproachDir.Y, 0) * (MinimumSpawnDistance + 500.0f); // Create formation positions TArray FormationPositions; switch (FormationType) { case 0: // V-formation for (int32 i = 0; i < 5; i++) { if (i == 0) // Leader { FormationPositions.Add(BaseSpawnPos); } else if (i % 2 == 1) // Left wing { FVector Offset(-ApproachDir.Y, ApproachDir.X, 0); FormationPositions.Add(BaseSpawnPos + Offset * FormationSpacing * ((i + 1) / 2)); } else // Right wing { FVector Offset(ApproachDir.Y, -ApproachDir.X, 0); FormationPositions.Add(BaseSpawnPos + Offset * FormationSpacing * (i / 2)); } } break; case 1: // Line formation for (int32 i = 0; i < 5; i++) { FVector2D PerpDir(-ApproachDir.Y, ApproachDir.X); FVector Offset(PerpDir.X, PerpDir.Y, 0); FormationPositions.Add(BaseSpawnPos + Offset * FormationSpacing * (i - 2)); } break; case 2: // Diamond formation FormationPositions.Add(BaseSpawnPos); // Center FVector2D PerpDir(-ApproachDir.Y, ApproachDir.X); // Top FormationPositions.Add(BaseSpawnPos + FVector(ApproachDir.X, ApproachDir.Y, 0) * FormationSpacing); // Bottom FormationPositions.Add(BaseSpawnPos - FVector(ApproachDir.X, ApproachDir.Y, 0) * FormationSpacing); // Left FormationPositions.Add(BaseSpawnPos + FVector(PerpDir.X, PerpDir.Y, 0) * FormationSpacing); // Right FormationPositions.Add(BaseSpawnPos - FVector(PerpDir.X, PerpDir.Y, 0) * FormationSpacing); break; } // Spawn enemies at formation positions for (const FVector& Position : FormationPositions) { // Ensure the spawn location is far enough from the player FVector SpawnLocation = EnsureMinimumSpawnDistance(Position, PlayerLocation); FRotator SpawnRotation = FRotator::ZeroRotator; FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AEnemySpaceship* NewEnemy = World->SpawnActor( EnemyClass, SpawnLocation, SpawnRotation, SpawnParams); if (NewEnemy) { RotateTowardsPlayer(NewEnemy, PlayerLocation); } } // Switch back to random pattern after a formation spawn CurrentPattern = ESpawnPattern::Random; } } void ASpaceShooterGameMode::SpawnEnemyFlanking() { UWorld* World = GetWorld(); if (!World || !EnemyClass) return; FVector PlayerLocation = GetPlayerLocation(); // Spawn enemies from multiple sides (2-3 sides) int32 NumSides = FMath::RandRange(2, 3); float BaseAngle = FMath::RandRange(0.0f, 2.0f * PI); for (int32 Side = 0; Side < NumSides; Side++) { // Calculate angle for this side float Angle = BaseAngle + (Side * (2.0f * PI / NumSides)); // Spawn 1-2 enemies from this side int32 NumEnemies = FMath::RandRange(1, 2); for (int32 i = 0; i < NumEnemies; i++) { // Add some variation to the spawn position float OffsetAngle = Angle + FMath::RandRange(-0.3f, 0.3f); FVector Direction(FMath::Cos(OffsetAngle), FMath::Sin(OffsetAngle), 0.0f); // Calculate spawn position FVector SpawnLocation = PlayerLocation + (Direction * (MinimumSpawnDistance + FMath::RandRange(0.0f, 300.0f))); FRotator SpawnRotation = FRotator::ZeroRotator; FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AEnemySpaceship* NewEnemy = World->SpawnActor( EnemyClass, SpawnLocation, SpawnRotation, SpawnParams); if (NewEnemy) { RotateTowardsPlayer(NewEnemy, PlayerLocation); } } } CurrentPattern = ESpawnPattern::Random; } FVector ASpaceShooterGameMode::GetRandomSpawnLocation() { FVector PlayerLocation = GetPlayerLocation(); // Generate a random angle in radians float RandomAngle = FMath::RandRange(0.0f, 2.0f * PI); // Create a direction vector from the random angle FVector Direction( FMath::Cos(RandomAngle), FMath::Sin(RandomAngle), 0.0f ); // Use the minimum spawn distance plus some random additional distance float SpawnDistance = MinimumSpawnDistance + FMath::RandRange(0.0f, 500.0f); // Calculate the spawn position return PlayerLocation + (Direction * SpawnDistance); } void ASpaceShooterGameMode::UpdateDifficulty() { // Make the game harder over time by decreasing spawn interval CurrentSpawnInterval = FMath::Max(0.5f, BaseEnemySpawnInterval * FMath::Pow(DifficultyScaling, GameTime / DifficultyInterval)); // Update the timer GetWorldTimerManager().SetTimer(EnemySpawnTimer, this, &ASpaceShooterGameMode::SpawnEnemy, CurrentSpawnInterval, true); // Debug message if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Yellow, FString::Printf(TEXT("Difficulty Updated: Spawn Interval = %.2f"), CurrentSpawnInterval)); } } FVector ASpaceShooterGameMode::GetPlayerLocation() { APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0); if (PlayerController && PlayerController->GetPawn()) { return PlayerController->GetPawn()->GetActorLocation(); } return FVector::ZeroVector; } void ASpaceShooterGameMode::RotateTowardsPlayer(AEnemySpaceship* Enemy, const FVector& PlayerLocation) { if (!Enemy) return; // Calculate direction to player FVector Direction = PlayerLocation - Enemy->GetActorLocation(); Direction.Z = 0; // Keep rotation in 2D plane Direction.Normalize(); // Convert to rotation FRotator NewRotation = Direction.Rotation(); // Set the enemy's rotation Enemy->SetActorRotation(NewRotation); } // New function to ensure enemies spawn at least MinimumSpawnDistance away from player FVector ASpaceShooterGameMode::EnsureMinimumSpawnDistance(const FVector& ProposedLocation, const FVector& PlayerLocation) { FVector Direction = ProposedLocation - PlayerLocation; Direction.Z = 0; // Keep on the same Z plane as the player float CurrentDistance = Direction.Size(); // If already far enough, return the proposed location if (CurrentDistance >= MinimumSpawnDistance) { return ProposedLocation; } // Otherwise, extend the vector to meet the minimum distance Direction.Normalize(); return PlayerLocation + Direction * MinimumSpawnDistance; } void ASpaceShooterGameMode::UpdateGameTimer() { if (bIsGameOver) return; RemainingTime -= 1.0f; if (RemainingTime <= 0.0f) { EndGame(); } } void ASpaceShooterGameMode::EndGame() { bIsGameOver = true; // Stop enemy spawning GetWorldTimerManager().ClearTimer(EnemySpawnTimer); // Clear existing enemies TArray FoundEnemies; UGameplayStatics::GetAllActorsOfClass(GetWorld(), AEnemySpaceship::StaticClass(), FoundEnemies); for (AActor* Enemy : FoundEnemies) { Enemy->Destroy(); } // Pause the game UGameplayStatics::SetGamePaused(GetWorld(), true); } void ASpaceShooterGameMode::IncrementKillCount() { if (!bIsGameOver) { KillCount++; } } void ASpaceShooterGameMode::RestartGame() { UGameplayStatics::OpenLevel(this, FName(*GetWorld()->GetName()), false); }