#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: // Spawn a single enemy at a random edge location { UWorld* World = GetWorld(); if (World && EnemyClass) { FVector SpawnLocation = GetScreenEdgeSpawnLocation(); 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() { // 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; // Choose a random direction for the wave float WaveAngle = FMath::RandRange(0.0f, 2.0f * PI); FVector2D EdgeDirection(FMath::Cos(WaveAngle), FMath::Sin(WaveAngle)); // Get player location for facing direction FVector PlayerLocation = GetPlayerLocation(); // Get screen bounds TArray ScreenBounds = GetScreenBounds(); float ScreenWidth = ScreenBounds[1].X - ScreenBounds[0].X; // Create a line of enemies perpendicular to the direction FVector2D PerpDirection(-EdgeDirection.Y, EdgeDirection.X); // Spawn wave of enemies - increased distance from 2000 to MinimumSpawnDistance for (int32 i = 0; i < WaveSize; i++) { FVector ProposedLocation; ProposedLocation.X = PlayerLocation.X + (EdgeDirection.X * MinimumSpawnDistance) + (PerpDirection.X * (i - WaveSize / 2) * FormationSpacing); ProposedLocation.Y = PlayerLocation.Y + (EdgeDirection.Y * MinimumSpawnDistance) + (PerpDirection.Y * (i - WaveSize / 2) * FormationSpacing); ProposedLocation.Z = PlayerLocation.Z; // Ensure the spawn location is far enough from the player FVector SpawnLocation = EnsureMinimumSpawnDistance(ProposedLocation, PlayerLocation); FRotator SpawnRotation = FRotator::ZeroRotator; FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AEnemySpaceship* NewEnemy = World->SpawnActor( EnemyClass, SpawnLocation, SpawnRotation, SpawnParams); if (NewEnemy) { RotateTowardsPlayer(NewEnemy, PlayerLocation); } } // Increase wave counter and possibly switch back to random 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() { // 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(); // Spawn enemies from multiple sides (usually 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)); FVector2D Direction(FMath::Cos(Angle), FMath::Sin(Angle)); // 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); FVector2D OffsetDir(FMath::Cos(OffsetAngle), FMath::Sin(OffsetAngle)); // Increased from 2000 to MinimumSpawnDistance FVector ProposedLocation = PlayerLocation + FVector(OffsetDir.X, OffsetDir.Y, 0) * MinimumSpawnDistance; // Ensure the spawn location is far enough from the player FVector SpawnLocation = EnsureMinimumSpawnDistance(ProposedLocation, PlayerLocation); FRotator SpawnRotation = FRotator::ZeroRotator; FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AEnemySpaceship* NewEnemy = World->SpawnActor( EnemyClass, SpawnLocation, SpawnRotation, SpawnParams); if (NewEnemy) { RotateTowardsPlayer(NewEnemy, PlayerLocation); } } } // Return to random spawning CurrentPattern = ESpawnPattern::Random; } } FVector ASpaceShooterGameMode::GetScreenEdgeSpawnLocation() { FVector PlayerLocation = GetPlayerLocation(); TArray ScreenBounds = GetScreenBounds(); // Decide which edge to spawn from (0 = top, 1 = right, 2 = bottom, 3 = left) int32 Edge = FMath::RandRange(0, 3); FVector SpawnLocation; float RandomPos; // Increased margin to spawn farther from screen edges float ExtendedMargin = ScreenSpawnMargin + 500.0f; switch (Edge) { case 0: // Top edge RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X); SpawnLocation = FVector(RandomPos, ScreenBounds[0].Y - ExtendedMargin, PlayerLocation.Z); break; case 1: // Right edge RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y); SpawnLocation = FVector(ScreenBounds[1].X + ExtendedMargin, RandomPos, PlayerLocation.Z); break; case 2: // Bottom edge RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X); SpawnLocation = FVector(RandomPos, ScreenBounds[1].Y + ExtendedMargin, PlayerLocation.Z); break; case 3: // Left edge RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y); SpawnLocation = FVector(ScreenBounds[0].X - ExtendedMargin, RandomPos, PlayerLocation.Z); break; } // Ensure the spawn location is far enough from the player return EnsureMinimumSpawnDistance(SpawnLocation, PlayerLocation); } FVector ASpaceShooterGameMode::GetSpawnZoneLocation() { // If no spawn zones are defined, return a screen edge location if (SpawnZones.Num() == 0) { return GetScreenEdgeSpawnLocation(); } // Filter active spawn zones TArray ActiveZones; float TotalWeight = 0.0f; for (const FSpawnZone& Zone : SpawnZones) { if (Zone.bActive) { ActiveZones.Add(Zone); TotalWeight += Zone.SpawnWeight; } } // If no active zones, return screen edge if (ActiveZones.Num() == 0) { return GetScreenEdgeSpawnLocation(); } // Select a zone based on weight float RandomWeight = FMath::RandRange(0.0f, TotalWeight); float WeightSum = 0.0f; FVector PlayerLocation = GetPlayerLocation(); for (const FSpawnZone& Zone : ActiveZones) { WeightSum += Zone.SpawnWeight; if (RandomWeight <= WeightSum) { // Generate random point within this zone's radius float RandomAngle = FMath::RandRange(0.0f, 2.0f * PI); float RandomRadius = FMath::RandRange(0.0f, Zone.Radius); FVector SpawnOffset( FMath::Cos(RandomAngle) * RandomRadius, FMath::Sin(RandomAngle) * RandomRadius, 0.0f ); FVector ProposedLocation = Zone.Location + SpawnOffset; // Ensure the spawn location is far enough from the player return EnsureMinimumSpawnDistance(ProposedLocation, PlayerLocation); } } // Fallback return GetScreenEdgeSpawnLocation(); } 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; } TArray ASpaceShooterGameMode::GetScreenBounds() { TArray Bounds; FVector2D ScreenMin, ScreenMax; // Get player controller for screen info APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0); if (!PlayerController) { // Fallback values if no controller Bounds.Add(FVector2D(-2000, -2000)); Bounds.Add(FVector2D(2000, 2000)); return Bounds; } // Get viewport size int32 ViewportSizeX, ViewportSizeY; PlayerController->GetViewportSize(ViewportSizeX, ViewportSizeY); // Get world location of screen corners FVector WorldLocation, WorldDirection; // Top-Left corner PlayerController->DeprojectScreenPositionToWorld(0, 0, WorldLocation, WorldDirection); ScreenMin = FVector2D(WorldLocation.X, WorldLocation.Y); // Bottom-Right corner PlayerController->DeprojectScreenPositionToWorld(ViewportSizeX, ViewportSizeY, WorldLocation, WorldDirection); ScreenMax = FVector2D(WorldLocation.X, WorldLocation.Y); // Add some margin ScreenMin.X -= 200; ScreenMin.Y -= 200; ScreenMax.X += 200; ScreenMax.Y += 200; Bounds.Add(ScreenMin); Bounds.Add(ScreenMax); return Bounds; } 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); }