Implement enemy shooting and scale down for more arcade game feel

This commit is contained in:
2025-04-15 22:21:28 +05:30
parent ca23fd129e
commit e2e709bffd
14 changed files with 886 additions and 41 deletions

View File

@@ -1,5 +1,14 @@
#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()
{
@@ -9,6 +18,11 @@ AEnemySpaceship::AEnemySpaceship()
EnemyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EnemyMesh"));
RootComponent = EnemyMesh;
// Create projectile spawn point
ProjectileSpawnPoint = CreateDefaultSubobject<USceneComponent>(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);
@@ -18,7 +32,7 @@ AEnemySpaceship::AEnemySpaceship()
EnemyMesh->SetCollisionObjectType(ECC_Pawn);
EnemyMesh->SetCollisionResponseToAllChannels(ECR_Block);
EnemyMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore); // Ignore other pawns
EnemyMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Ignore); // Ignore player
EnemyMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Block); // Block player (for damage)
EnemyMesh->SetGenerateOverlapEvents(true);
}
@@ -26,27 +40,253 @@ void AEnemySpaceship::BeginPlay()
{
Super::BeginPlay();
// Find the player pawn
PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
// Find the player pawn and cast to SpaceshipPawn
AActor* FoundPlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
PlayerPawn = Cast<ASpaceshipPawn>(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<ASpaceshipPawn>(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<EEnemyBehaviorState>(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)
{
// Move towards the player
// 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<AEnemyProjectile>(
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)
{
@@ -58,7 +298,18 @@ float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& Damage
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red,
FString::Printf(TEXT("Enemy Health: %f"), CurrentHealth));
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)
@@ -71,7 +322,21 @@ float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& Damage
void AEnemySpaceship::Die()
{
// Add any death effects here
// 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();