Add deleted sphere model back
This commit is contained in:
Binary file not shown.
BIN
Content/Blueprints/WBP_Crosshair.uasset
LFS
Normal file
BIN
Content/Blueprints/WBP_Crosshair.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/EnvironmentLevel.umap
LFS
BIN
Content/EnvironmentLevel.umap
LFS
Binary file not shown.
BIN
Content/Input/IA_Pause.uasset
LFS
Normal file
BIN
Content/Input/IA_Pause.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/Main.umap
LFS
BIN
Content/Main.umap
LFS
Binary file not shown.
BIN
Content/Meshes/Shape_Sphere.uasset
LFS
Normal file
BIN
Content/Meshes/Shape_Sphere.uasset
LFS
Normal file
Binary file not shown.
@@ -10,7 +10,7 @@ public class MyProject3 : ModuleRules
|
|||||||
|
|
||||||
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
|
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
|
||||||
|
|
||||||
PrivateDependencyModuleNames.AddRange(new string[] { "EnhancedInput" });
|
PrivateDependencyModuleNames.AddRange(new string[] { "EnhancedInput", "UMG" });
|
||||||
|
|
||||||
// Uncomment if you are using Slate UI
|
// Uncomment if you are using Slate UI
|
||||||
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
|
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
#include "SpaceshipPawn.h"
|
#include "SpaceshipPawn.h"
|
||||||
#include "EnemySpaceship.h"
|
#include "EnemySpaceship.h"
|
||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
|
||||||
ASpaceShooterGameMode::ASpaceShooterGameMode()
|
ASpaceShooterGameMode::ASpaceShooterGameMode()
|
||||||
{
|
{
|
||||||
@@ -22,6 +24,18 @@ ASpaceShooterGameMode::ASpaceShooterGameMode()
|
|||||||
// Enable Tick()
|
// Enable Tick()
|
||||||
PrimaryActorTick.bCanEverTick = true;
|
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
|
// Debug message
|
||||||
if (GEngine)
|
if (GEngine)
|
||||||
{
|
{
|
||||||
@@ -35,7 +49,11 @@ void ASpaceShooterGameMode::StartPlay()
|
|||||||
|
|
||||||
// Start spawning enemies
|
// Start spawning enemies
|
||||||
GetWorldTimerManager().SetTimer(EnemySpawnTimer, this, &ASpaceShooterGameMode::SpawnEnemy,
|
GetWorldTimerManager().SetTimer(EnemySpawnTimer, this, &ASpaceShooterGameMode::SpawnEnemy,
|
||||||
EnemySpawnInterval, true);
|
CurrentSpawnInterval, true);
|
||||||
|
|
||||||
|
// Start difficulty scaling
|
||||||
|
GetWorldTimerManager().SetTimer(DifficultyTimer, this, &ASpaceShooterGameMode::UpdateDifficulty,
|
||||||
|
DifficultyInterval, true);
|
||||||
|
|
||||||
// Debug message
|
// Debug message
|
||||||
if (GEngine)
|
if (GEngine)
|
||||||
@@ -47,6 +65,26 @@ void ASpaceShooterGameMode::StartPlay()
|
|||||||
void ASpaceShooterGameMode::Tick(float DeltaTime)
|
void ASpaceShooterGameMode::Tick(float DeltaTime)
|
||||||
{
|
{
|
||||||
Super::Tick(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()
|
void ASpaceShooterGameMode::SpawnEnemy()
|
||||||
@@ -58,42 +96,412 @@ void ASpaceShooterGameMode::SpawnEnemy()
|
|||||||
// Only spawn if we haven't reached the maximum
|
// Only spawn if we haven't reached the maximum
|
||||||
if (FoundEnemies.Num() < MaxEnemies)
|
if (FoundEnemies.Num() < MaxEnemies)
|
||||||
{
|
{
|
||||||
UWorld* World = GetWorld();
|
switch (CurrentPattern)
|
||||||
if (World && EnemyClass)
|
|
||||||
{
|
{
|
||||||
FVector SpawnLocation = GetRandomSpawnLocation();
|
case ESpawnPattern::Random:
|
||||||
FRotator SpawnRotation = FRotator::ZeroRotator;
|
// Spawn a single enemy at a random edge location
|
||||||
FActorSpawnParameters SpawnParams;
|
{
|
||||||
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
|
UWorld* World = GetWorld();
|
||||||
|
if (World && EnemyClass)
|
||||||
|
{
|
||||||
|
FVector SpawnLocation = GetScreenEdgeSpawnLocation();
|
||||||
|
FRotator SpawnRotation = FRotator::ZeroRotator;
|
||||||
|
FActorSpawnParameters SpawnParams;
|
||||||
|
SpawnParams.SpawnCollisionHandlingOverride =
|
||||||
|
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
|
||||||
|
|
||||||
// Spawn using the Blueprint class instead of the C++ class directly
|
AEnemySpaceship* NewEnemy = World->SpawnActor<AEnemySpaceship>(
|
||||||
AEnemySpaceship* NewEnemy = World->SpawnActor<AEnemySpaceship>(EnemyClass, SpawnLocation,
|
EnemyClass, SpawnLocation, SpawnRotation, SpawnParams);
|
||||||
SpawnRotation, SpawnParams);
|
|
||||||
|
if (NewEnemy)
|
||||||
|
{
|
||||||
|
RotateTowardsPlayer(NewEnemy, GetPlayerLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESpawnPattern::Wave:
|
||||||
|
SpawnEnemyWave();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESpawnPattern::Formation:
|
||||||
|
SpawnEnemyFormation();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESpawnPattern::Flanking:
|
||||||
|
SpawnEnemyFlanking();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FVector ASpaceShooterGameMode::GetRandomSpawnLocation()
|
void ASpaceShooterGameMode::SpawnEnemyWave()
|
||||||
|
{
|
||||||
|
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<FVector2D> 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
|
||||||
|
for (int32 i = 0; i < WaveSize; i++)
|
||||||
|
{
|
||||||
|
FVector SpawnLocation;
|
||||||
|
SpawnLocation.X = PlayerLocation.X + (EdgeDirection.X * 2000.0f) +
|
||||||
|
(PerpDirection.X * (i - WaveSize / 2) * FormationSpacing);
|
||||||
|
SpawnLocation.Y = PlayerLocation.Y + (EdgeDirection.Y * 2000.0f) +
|
||||||
|
(PerpDirection.Y * (i - WaveSize / 2) * FormationSpacing);
|
||||||
|
SpawnLocation.Z = PlayerLocation.Z;
|
||||||
|
|
||||||
|
FRotator SpawnRotation = FRotator::ZeroRotator;
|
||||||
|
FActorSpawnParameters SpawnParams;
|
||||||
|
SpawnParams.SpawnCollisionHandlingOverride =
|
||||||
|
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
|
||||||
|
|
||||||
|
AEnemySpaceship* NewEnemy = World->SpawnActor<AEnemySpaceship>(
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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
|
||||||
|
FVector BaseSpawnPos = PlayerLocation + FVector(ApproachDir.X, ApproachDir.Y, 0) * 2500.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)
|
||||||
|
{
|
||||||
|
FRotator SpawnRotation = FRotator::ZeroRotator;
|
||||||
|
FActorSpawnParameters SpawnParams;
|
||||||
|
SpawnParams.SpawnCollisionHandlingOverride =
|
||||||
|
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
|
||||||
|
|
||||||
|
AEnemySpaceship* NewEnemy = World->SpawnActor<AEnemySpaceship>(
|
||||||
|
EnemyClass, Position, 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;
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
FVector SpawnLocation = PlayerLocation + FVector(OffsetDir.X, OffsetDir.Y, 0) * 2000.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return to random spawning
|
||||||
|
CurrentPattern = ESpawnPattern::Random;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector ASpaceShooterGameMode::GetScreenEdgeSpawnLocation()
|
||||||
|
{
|
||||||
|
FVector PlayerLocation = GetPlayerLocation();
|
||||||
|
TArray<FVector2D> 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;
|
||||||
|
|
||||||
|
switch (Edge)
|
||||||
|
{
|
||||||
|
case 0: // Top edge
|
||||||
|
RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X);
|
||||||
|
SpawnLocation = FVector(RandomPos, ScreenBounds[0].Y - ScreenSpawnMargin, PlayerLocation.Z);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // Right edge
|
||||||
|
RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y);
|
||||||
|
SpawnLocation = FVector(ScreenBounds[1].X + ScreenSpawnMargin, RandomPos, PlayerLocation.Z);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // Bottom edge
|
||||||
|
RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X);
|
||||||
|
SpawnLocation = FVector(RandomPos, ScreenBounds[1].Y + ScreenSpawnMargin, PlayerLocation.Z);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // Left edge
|
||||||
|
RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y);
|
||||||
|
SpawnLocation = FVector(ScreenBounds[0].X - ScreenSpawnMargin, RandomPos, PlayerLocation.Z);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpawnLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<FSpawnZone> 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;
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
return Zone.Location + SpawnOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||||
if (PlayerController && PlayerController->GetPawn())
|
if (PlayerController && PlayerController->GetPawn())
|
||||||
{
|
{
|
||||||
FVector PlayerLocation = PlayerController->GetPawn()->GetActorLocation();
|
return PlayerController->GetPawn()->GetActorLocation();
|
||||||
|
}
|
||||||
|
return FVector::ZeroVector;
|
||||||
|
}
|
||||||
|
|
||||||
// Generate random angle
|
TArray<FVector2D> ASpaceShooterGameMode::GetScreenBounds()
|
||||||
float Angle = FMath::RandRange(0.0f, 2.0f * PI);
|
{
|
||||||
|
TArray<FVector2D> Bounds;
|
||||||
|
FVector2D ScreenMin, ScreenMax;
|
||||||
|
|
||||||
// Generate random radius between min and max
|
// Get player controller for screen info
|
||||||
float Radius = FMath::RandRange(MinSpawnRadius, MaxSpawnRadius);
|
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||||
|
if (!PlayerController)
|
||||||
// Calculate spawn position in a circle around the player
|
{
|
||||||
FVector SpawnLocation;
|
// Fallback values if no controller
|
||||||
SpawnLocation.X = PlayerLocation.X + Radius * FMath::Cos(Angle);
|
Bounds.Add(FVector2D(-2000, -2000));
|
||||||
SpawnLocation.Y = PlayerLocation.Y + Radius * FMath::Sin(Angle);
|
Bounds.Add(FVector2D(2000, 2000));
|
||||||
SpawnLocation.Z = PlayerLocation.Z;
|
return Bounds;
|
||||||
|
|
||||||
return SpawnLocation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return FVector::ZeroVector;
|
// 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);
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,35 @@
|
|||||||
#include "GameFramework/GameModeBase.h"
|
#include "GameFramework/GameModeBase.h"
|
||||||
#include "SpaceShooterGameMode.generated.h"
|
#include "SpaceShooterGameMode.generated.h"
|
||||||
|
|
||||||
|
// Enum for different spawn patterns
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class ESpawnPattern : uint8
|
||||||
|
{
|
||||||
|
Random, // Randomly at screen edges
|
||||||
|
Wave, // Groups of enemies from one direction
|
||||||
|
Formation, // Specific formations (V-shape, line, etc.)
|
||||||
|
Flanking // Enemies from multiple sides simultaneously
|
||||||
|
};
|
||||||
|
|
||||||
|
// Struct to define a spawn point/zone
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FSpawnZone
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
FVector Location;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
float Radius = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
float SpawnWeight = 1.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
bool bActive = true;
|
||||||
|
};
|
||||||
|
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class MYPROJECT3_API ASpaceShooterGameMode : public AGameModeBase
|
class MYPROJECT3_API ASpaceShooterGameMode : public AGameModeBase
|
||||||
{
|
{
|
||||||
@@ -20,19 +49,48 @@ protected:
|
|||||||
TSubclassOf<class AEnemySpaceship> EnemyClass;
|
TSubclassOf<class AEnemySpaceship> EnemyClass;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
float EnemySpawnInterval = 2.0f;
|
float BaseEnemySpawnInterval = 2.0f;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
int32 MaxEnemies = 10;
|
int32 MaxEnemies = 10;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
float MinSpawnRadius = 1000.0f;
|
float ScreenSpawnMargin = 100.0f;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
float MaxSpawnRadius = 2000.0f;
|
TArray<FSpawnZone> SpawnZones;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
|
TArray<ESpawnPattern> SpawnPatterns;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
|
int32 WaveSize = 3;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
|
float FormationSpacing = 150.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning|Difficulty")
|
||||||
|
float DifficultyScaling = 0.95f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning|Difficulty")
|
||||||
|
int32 DifficultyInterval = 30;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FTimerHandle EnemySpawnTimer;
|
FTimerHandle EnemySpawnTimer;
|
||||||
|
FTimerHandle DifficultyTimer;
|
||||||
|
float CurrentSpawnInterval;
|
||||||
|
ESpawnPattern CurrentPattern;
|
||||||
|
int32 CurrentWaveCount;
|
||||||
|
float GameTime;
|
||||||
|
|
||||||
void SpawnEnemy();
|
void SpawnEnemy();
|
||||||
FVector GetRandomSpawnLocation();
|
void SpawnEnemyWave();
|
||||||
|
void SpawnEnemyFormation();
|
||||||
|
void SpawnEnemyFlanking();
|
||||||
|
FVector GetScreenEdgeSpawnLocation();
|
||||||
|
FVector GetSpawnZoneLocation();
|
||||||
|
void UpdateDifficulty();
|
||||||
|
FVector GetPlayerLocation();
|
||||||
|
TArray<FVector2D> GetScreenBounds();
|
||||||
|
void RotateTowardsPlayer(AEnemySpaceship* Enemy, const FVector& PlayerLocation);
|
||||||
};
|
};
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "SpaceshipProjectile.h"
|
#include "SpaceshipProjectile.h"
|
||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
#include "GameFramework/GameUserSettings.h"
|
#include "GameFramework/GameUserSettings.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
|
||||||
|
|
||||||
ASpaceshipPawn::ASpaceshipPawn()
|
ASpaceshipPawn::ASpaceshipPawn()
|
||||||
@@ -65,25 +66,6 @@ void ASpaceshipPawn::BeginPlay()
|
|||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
|
|
||||||
// Debug messages for setup verification
|
|
||||||
if (GEngine)
|
|
||||||
{
|
|
||||||
if (ProjectileClass)
|
|
||||||
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("ProjectileClass is set"));
|
|
||||||
else
|
|
||||||
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("ProjectileClass is NOT set"));
|
|
||||||
|
|
||||||
if (ShootAction)
|
|
||||||
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("ShootAction is set"));
|
|
||||||
else
|
|
||||||
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("ShootAction is NOT set"));
|
|
||||||
|
|
||||||
if (ProjectileSpawnPoint)
|
|
||||||
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("ProjectileSpawnPoint is set"));
|
|
||||||
else
|
|
||||||
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("ProjectileSpawnPoint is NOT set"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store player controller reference
|
// Store player controller reference
|
||||||
PlayerControllerRef = Cast<APlayerController>(Controller);
|
PlayerControllerRef = Cast<APlayerController>(Controller);
|
||||||
|
|
||||||
@@ -108,6 +90,20 @@ void ASpaceshipPawn::BeginPlay()
|
|||||||
GameUserSettings->ApplySettings(false);
|
GameUserSettings->ApplySettings(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and add crosshair widget to viewport
|
||||||
|
if (CrosshairWidgetClass)
|
||||||
|
{
|
||||||
|
APlayerController* PlayerController = Cast<APlayerController>(GetController());
|
||||||
|
if (PlayerController)
|
||||||
|
{
|
||||||
|
CrosshairWidget = CreateWidget<UUserWidget>(PlayerController, CrosshairWidgetClass);
|
||||||
|
if (CrosshairWidget)
|
||||||
|
{
|
||||||
|
CrosshairWidget->AddToViewport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASpaceshipPawn::Tick(float DeltaTime)
|
void ASpaceshipPawn::Tick(float DeltaTime)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "GameFramework/Pawn.h"
|
#include "GameFramework/Pawn.h"
|
||||||
#include "InputActionValue.h"
|
#include "InputActionValue.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
#include "SpaceshipPawn.generated.h"
|
#include "SpaceshipPawn.generated.h"
|
||||||
|
|
||||||
UCLASS()
|
UCLASS()
|
||||||
@@ -78,6 +79,12 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
|
||||||
class UInputAction* ShootAction;
|
class UInputAction* ShootAction;
|
||||||
|
|
||||||
|
UPROPERTY(EditDefaultsOnly, Category = "UI")
|
||||||
|
TSubclassOf<UUserWidget> CrosshairWidgetClass;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
UUserWidget* CrosshairWidget;
|
||||||
|
|
||||||
// Input functions
|
// Input functions
|
||||||
void HandleThrottleStarted(const FInputActionValue& Value);
|
void HandleThrottleStarted(const FInputActionValue& Value);
|
||||||
void HandleThrottleReleased(const FInputActionValue& Value);
|
void HandleThrottleReleased(const FInputActionValue& Value);
|
||||||
|
|||||||
Reference in New Issue
Block a user