Files
Yamato/Source/MyProject3/SpaceShooterGameMode.cpp

468 lines
15 KiB
C++

#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<APawn> PlayerPawnBPClass(TEXT("/Game/Blueprints/BP_SpaceshipPawn"));
if (PlayerPawnBPClass.Class != nullptr)
{
DefaultPawnClass = PlayerPawnBPClass.Class;
}
// Find enemy blueprint class
static ConstructorHelpers::FClassFinder<AEnemySpaceship> 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<AActor*> 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<AEnemySpaceship>(
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<AEnemySpaceship>(
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<AActor*> 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<FVector> 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<AEnemySpaceship>(
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<AEnemySpaceship>(
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<AActor*> 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);
}