Compare commits
7 Commits
ca23fd129e
...
Environmen
| Author | SHA1 | Date | |
|---|---|---|---|
| bc03fd5091 | |||
| e2e709bffd | |||
|
|
39ace4311d | ||
|
|
e2c4db236f | ||
|
|
22aea1380f | ||
|
|
08b6dd45fa | ||
|
|
d0a4f6effc |
BIN
Content/Blueprints/BP_EnemyProjectile.uasset
LFS
Normal file
BIN
Content/Blueprints/BP_EnemyProjectile.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/EnvironmentAssests/RingField.fbx
LFS
Normal file
BIN
Content/EnvironmentAssests/RingField.fbx
LFS
Normal file
Binary file not shown.
BIN
Content/EnvironmentAssests/RingField.uasset
LFS
Normal file
BIN
Content/EnvironmentAssests/RingField.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/EnvironmentAssests/Ring_M.uasset
LFS
Normal file
BIN
Content/EnvironmentAssests/Ring_M.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/EnvironmentLevel.umap
LFS
BIN
Content/EnvironmentLevel.umap
LFS
Binary file not shown.
BIN
Content/Main.umap
LFS
BIN
Content/Main.umap
LFS
Binary file not shown.
156
Source/MyProject3/EnemyProjectile.cpp
Normal file
156
Source/MyProject3/EnemyProjectile.cpp
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#include "EnemyProjectile.h"
|
||||||
|
#include "GameFramework/ProjectileMovementComponent.h"
|
||||||
|
#include "Components/StaticMeshComponent.h"
|
||||||
|
#include "SpaceshipPawn.h" // Include the player ship header
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
#include "Engine/Engine.h"
|
||||||
|
|
||||||
|
AEnemyProjectile::AEnemyProjectile()
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
|
||||||
|
// Create and setup the projectile mesh
|
||||||
|
ProjectileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMesh"));
|
||||||
|
RootComponent = ProjectileMesh;
|
||||||
|
ProjectileMesh->SetCollisionProfileName(TEXT("EnemyProjectile"));
|
||||||
|
|
||||||
|
// Set up collision
|
||||||
|
ProjectileMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||||
|
ProjectileMesh->SetCollisionObjectType(ECC_WorldDynamic);
|
||||||
|
ProjectileMesh->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||||
|
|
||||||
|
// Block GameTraceChannel1 (player ship)
|
||||||
|
ProjectileMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Block);
|
||||||
|
|
||||||
|
// Block world static for environment collisions
|
||||||
|
ProjectileMesh->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block);
|
||||||
|
|
||||||
|
// Block pawns for player ship collision
|
||||||
|
ProjectileMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block);
|
||||||
|
|
||||||
|
// Enable overlap events
|
||||||
|
ProjectileMesh->SetGenerateOverlapEvents(true);
|
||||||
|
|
||||||
|
// Bind hit event
|
||||||
|
ProjectileMesh->OnComponentHit.AddDynamic(this, &AEnemyProjectile::OnHit);
|
||||||
|
|
||||||
|
// Bind overlap event for redundant collision detection
|
||||||
|
ProjectileMesh->OnComponentBeginOverlap.AddDynamic(this, &AEnemyProjectile::OnOverlapBegin);
|
||||||
|
|
||||||
|
// Create and setup projectile movement
|
||||||
|
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovement"));
|
||||||
|
ProjectileMovement->UpdatedComponent = ProjectileMesh;
|
||||||
|
ProjectileMovement->InitialSpeed = ProjectileSpeed;
|
||||||
|
ProjectileMovement->MaxSpeed = ProjectileSpeed;
|
||||||
|
ProjectileMovement->bRotationFollowsVelocity = true;
|
||||||
|
ProjectileMovement->ProjectileGravityScale = 0.0f;
|
||||||
|
|
||||||
|
// Important: Enable sweep to prevent tunneling at high speeds
|
||||||
|
ProjectileMovement->bSweepCollision = true;
|
||||||
|
|
||||||
|
// Set lifetime
|
||||||
|
InitialLifeSpan = 3.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AEnemyProjectile::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
// Debug message to verify projectile spawned
|
||||||
|
if (GEngine)
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Blue, TEXT("Enemy Projectile Spawned"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AEnemyProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
|
||||||
|
FVector NormalImpulse, const FHitResult& Hit)
|
||||||
|
{
|
||||||
|
// Debug message to verify hit detection
|
||||||
|
if (GEngine && OtherActor)
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Orange,
|
||||||
|
FString::Printf(TEXT("Enemy Projectile hit: %s"), *OtherActor->GetName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OtherActor && OtherActor != this)
|
||||||
|
{
|
||||||
|
// Check if we hit a player ship
|
||||||
|
ASpaceshipPawn* Player = Cast<ASpaceshipPawn>(OtherActor);
|
||||||
|
if (Player)
|
||||||
|
{
|
||||||
|
// Apply damage to player
|
||||||
|
FDamageEvent DamageEvent;
|
||||||
|
float ActualDamage = Player->TakeDamage(DamageAmount, DamageEvent, nullptr, this);
|
||||||
|
|
||||||
|
// Debug message for damage
|
||||||
|
if (GEngine)
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red,
|
||||||
|
FString::Printf(TEXT("Dealt %.1f damage to player"), ActualDamage));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn impact effect if set
|
||||||
|
if (ImpactEffect)
|
||||||
|
{
|
||||||
|
UGameplayStatics::SpawnEmitterAtLocation(
|
||||||
|
GetWorld(),
|
||||||
|
ImpactEffect,
|
||||||
|
Hit.Location,
|
||||||
|
Hit.Normal.Rotation()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the projectile
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AEnemyProjectile::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
|
||||||
|
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
|
||||||
|
bool bFromSweep, const FHitResult& SweepResult)
|
||||||
|
{
|
||||||
|
// This is a redundant collision detection method
|
||||||
|
// Some fast-moving projectiles might miss hit events but catch overlap events
|
||||||
|
|
||||||
|
// Debug message to verify overlap detection
|
||||||
|
if (GEngine && OtherActor)
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Yellow,
|
||||||
|
FString::Printf(TEXT("Enemy Projectile overlap with: %s"), *OtherActor->GetName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OtherActor && OtherActor != this)
|
||||||
|
{
|
||||||
|
// Check if we overlap with a player ship
|
||||||
|
ASpaceshipPawn* Player = Cast<ASpaceshipPawn>(OtherActor);
|
||||||
|
if (Player)
|
||||||
|
{
|
||||||
|
// Apply damage to player
|
||||||
|
FDamageEvent DamageEvent;
|
||||||
|
float ActualDamage = Player->TakeDamage(DamageAmount, DamageEvent, nullptr, this);
|
||||||
|
|
||||||
|
// Debug message for damage
|
||||||
|
if (GEngine)
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red,
|
||||||
|
FString::Printf(TEXT("Overlap - Dealt %.1f damage to player"), ActualDamage));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn impact effect if set
|
||||||
|
if (ImpactEffect)
|
||||||
|
{
|
||||||
|
UGameplayStatics::SpawnEmitterAtLocation(
|
||||||
|
GetWorld(),
|
||||||
|
ImpactEffect,
|
||||||
|
GetActorLocation(),
|
||||||
|
GetActorRotation()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the projectile
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Source/MyProject3/EnemyProjectile.h
Normal file
43
Source/MyProject3/EnemyProjectile.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "EnemyProjectile.generated.h"
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class MYPROJECT3_API AEnemyProjectile : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
AEnemyProjectile();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components")
|
||||||
|
class UStaticMeshComponent* ProjectileMesh;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components")
|
||||||
|
class UProjectileMovementComponent* ProjectileMovement;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
|
||||||
|
float ProjectileSpeed = 2500.0f; // Slightly slower than player projectiles
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
|
||||||
|
float DamageAmount = 10.0f; // Less damage than player projectiles
|
||||||
|
|
||||||
|
// Add a particle effect for impact
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||||
|
class UParticleSystem* ImpactEffect;
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
|
||||||
|
FVector NormalImpulse, const FHitResult& Hit);
|
||||||
|
|
||||||
|
// Add an overlap handler for redundant collision detection
|
||||||
|
UFUNCTION()
|
||||||
|
void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
|
||||||
|
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
|
||||||
|
bool bFromSweep, const FHitResult& SweepResult);
|
||||||
|
};
|
||||||
@@ -1,5 +1,14 @@
|
|||||||
#include "EnemySpaceship.h"
|
#include "EnemySpaceship.h"
|
||||||
#include "Kismet/GameplayStatics.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()
|
AEnemySpaceship::AEnemySpaceship()
|
||||||
{
|
{
|
||||||
@@ -9,6 +18,11 @@ AEnemySpaceship::AEnemySpaceship()
|
|||||||
EnemyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EnemyMesh"));
|
EnemyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EnemyMesh"));
|
||||||
RootComponent = 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
|
// Disable gravity and physics simulation
|
||||||
EnemyMesh->SetSimulatePhysics(false);
|
EnemyMesh->SetSimulatePhysics(false);
|
||||||
EnemyMesh->SetEnableGravity(false);
|
EnemyMesh->SetEnableGravity(false);
|
||||||
@@ -18,7 +32,7 @@ AEnemySpaceship::AEnemySpaceship()
|
|||||||
EnemyMesh->SetCollisionObjectType(ECC_Pawn);
|
EnemyMesh->SetCollisionObjectType(ECC_Pawn);
|
||||||
EnemyMesh->SetCollisionResponseToAllChannels(ECR_Block);
|
EnemyMesh->SetCollisionResponseToAllChannels(ECR_Block);
|
||||||
EnemyMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore); // Ignore other pawns
|
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);
|
EnemyMesh->SetGenerateOverlapEvents(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,27 +40,253 @@ void AEnemySpaceship::BeginPlay()
|
|||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
|
|
||||||
// Find the player pawn
|
// Find the player pawn and cast to SpaceshipPawn
|
||||||
PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
|
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)
|
void AEnemySpaceship::Tick(float DeltaTime)
|
||||||
{
|
{
|
||||||
Super::Tick(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)
|
if (PlayerPawn)
|
||||||
{
|
{
|
||||||
// Move towards the player
|
// Calculate direction to player
|
||||||
FVector Direction = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal();
|
FVector Direction = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal();
|
||||||
|
|
||||||
|
// Move towards the player
|
||||||
FVector NewLocation = GetActorLocation() + Direction * MovementSpeed * DeltaTime;
|
FVector NewLocation = GetActorLocation() + Direction * MovementSpeed * DeltaTime;
|
||||||
SetActorLocation(NewLocation);
|
SetActorLocation(NewLocation);
|
||||||
|
|
||||||
// Face towards the player
|
// Face towards the player
|
||||||
FRotator NewRotation = Direction.Rotation();
|
FRotator NewRotation = Direction.Rotation();
|
||||||
SetActorRotation(NewRotation);
|
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,
|
float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
|
||||||
AController* EventInstigator, AActor* DamageCauser)
|
AController* EventInstigator, AActor* DamageCauser)
|
||||||
{
|
{
|
||||||
@@ -58,7 +298,18 @@ float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& Damage
|
|||||||
if (GEngine)
|
if (GEngine)
|
||||||
{
|
{
|
||||||
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red,
|
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)
|
if (CurrentHealth <= 0)
|
||||||
@@ -71,7 +322,21 @@ float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& Damage
|
|||||||
|
|
||||||
void AEnemySpaceship::Die()
|
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 the enemy
|
||||||
Destroy();
|
Destroy();
|
||||||
|
|||||||
@@ -4,6 +4,16 @@
|
|||||||
#include "GameFramework/Actor.h"
|
#include "GameFramework/Actor.h"
|
||||||
#include "EnemySpaceship.generated.h"
|
#include "EnemySpaceship.generated.h"
|
||||||
|
|
||||||
|
// Define AI behavior states
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EEnemyBehaviorState : uint8
|
||||||
|
{
|
||||||
|
Chase, // Actively pursue the player
|
||||||
|
Attack, // Stop and shoot at the player
|
||||||
|
Retreat, // Move away from player when too close
|
||||||
|
Strafe // Move sideways while attacking
|
||||||
|
};
|
||||||
|
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class MYPROJECT3_API AEnemySpaceship : public AActor
|
class MYPROJECT3_API AEnemySpaceship : public AActor
|
||||||
{
|
{
|
||||||
@@ -18,8 +28,21 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components")
|
||||||
UStaticMeshComponent* EnemyMesh;
|
UStaticMeshComponent* EnemyMesh;
|
||||||
|
|
||||||
|
// Projectile spawn points
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
USceneComponent* ProjectileSpawnPoint;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
||||||
float MovementSpeed = 300.0f;
|
float MovementSpeed = 500.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
||||||
|
float AttackRange = 1500.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
||||||
|
float MinDistanceToPlayer = 500.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
||||||
|
float StrafeSpeed = 300.0f;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
float MaxHealth = 100.0f;
|
float MaxHealth = 100.0f;
|
||||||
@@ -27,6 +50,30 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
float CurrentHealth = 100.0f;
|
float CurrentHealth = 100.0f;
|
||||||
|
|
||||||
|
// Projectile class to spawn
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
TSubclassOf<class AEnemyProjectile> ProjectileClass;
|
||||||
|
|
||||||
|
// Firing properties
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
float FireRate = 1.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
float ProjectileSpeed = 2000.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
float ProjectileDamage = 10.0f;
|
||||||
|
|
||||||
|
// AI behavior properties
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
|
||||||
|
EEnemyBehaviorState CurrentBehaviorState = EEnemyBehaviorState::Chase;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
|
||||||
|
float BehaviorChangeTime = 3.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
|
||||||
|
float StateChangeChance = 0.3f;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void Tick(float DeltaTime) override;
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
|
||||||
@@ -35,6 +82,32 @@ public:
|
|||||||
class AController* EventInstigator, AActor* DamageCauser) override;
|
class AController* EventInstigator, AActor* DamageCauser) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AActor* PlayerPawn;
|
// Reference to player
|
||||||
|
class ASpaceshipPawn* PlayerPawn;
|
||||||
|
|
||||||
|
// Distance to player
|
||||||
|
float DistanceToPlayer;
|
||||||
|
|
||||||
|
// Fire control
|
||||||
|
FTimerHandle FireTimerHandle;
|
||||||
|
bool bCanFire = true;
|
||||||
|
|
||||||
|
// Strafe direction (1 = right, -1 = left)
|
||||||
|
float StrafeDirection = 1.0f;
|
||||||
|
|
||||||
|
// Timer for changing behavior
|
||||||
|
FTimerHandle BehaviorTimerHandle;
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
void Fire();
|
||||||
|
void ResetFire();
|
||||||
|
void UpdateBehaviorState();
|
||||||
|
void ChangeBehaviorState();
|
||||||
void Die();
|
void Die();
|
||||||
|
|
||||||
|
// AI behavior implementation functions
|
||||||
|
void PerformChase(float DeltaTime);
|
||||||
|
void PerformAttack(float DeltaTime);
|
||||||
|
void PerformRetreat(float DeltaTime);
|
||||||
|
void PerformStrafe(float DeltaTime);
|
||||||
};
|
};
|
||||||
@@ -156,15 +156,18 @@ void ASpaceShooterGameMode::SpawnEnemyWave()
|
|||||||
// Create a line of enemies perpendicular to the direction
|
// Create a line of enemies perpendicular to the direction
|
||||||
FVector2D PerpDirection(-EdgeDirection.Y, EdgeDirection.X);
|
FVector2D PerpDirection(-EdgeDirection.Y, EdgeDirection.X);
|
||||||
|
|
||||||
// Spawn wave of enemies
|
// Spawn wave of enemies - increased distance from 2000 to MinimumSpawnDistance
|
||||||
for (int32 i = 0; i < WaveSize; i++)
|
for (int32 i = 0; i < WaveSize; i++)
|
||||||
{
|
{
|
||||||
FVector SpawnLocation;
|
FVector ProposedLocation;
|
||||||
SpawnLocation.X = PlayerLocation.X + (EdgeDirection.X * 2000.0f) +
|
ProposedLocation.X = PlayerLocation.X + (EdgeDirection.X * MinimumSpawnDistance) +
|
||||||
(PerpDirection.X * (i - WaveSize / 2) * FormationSpacing);
|
(PerpDirection.X * (i - WaveSize / 2) * FormationSpacing);
|
||||||
SpawnLocation.Y = PlayerLocation.Y + (EdgeDirection.Y * 2000.0f) +
|
ProposedLocation.Y = PlayerLocation.Y + (EdgeDirection.Y * MinimumSpawnDistance) +
|
||||||
(PerpDirection.Y * (i - WaveSize / 2) * FormationSpacing);
|
(PerpDirection.Y * (i - WaveSize / 2) * FormationSpacing);
|
||||||
SpawnLocation.Z = PlayerLocation.Z;
|
ProposedLocation.Z = PlayerLocation.Z;
|
||||||
|
|
||||||
|
// Ensure the spawn location is far enough from the player
|
||||||
|
FVector SpawnLocation = EnsureMinimumSpawnDistance(ProposedLocation, PlayerLocation);
|
||||||
|
|
||||||
FRotator SpawnRotation = FRotator::ZeroRotator;
|
FRotator SpawnRotation = FRotator::ZeroRotator;
|
||||||
FActorSpawnParameters SpawnParams;
|
FActorSpawnParameters SpawnParams;
|
||||||
@@ -205,8 +208,8 @@ void ASpaceShooterGameMode::SpawnEnemyFormation()
|
|||||||
float ApproachAngle = FMath::RandRange(0.0f, 2.0f * PI);
|
float ApproachAngle = FMath::RandRange(0.0f, 2.0f * PI);
|
||||||
FVector2D ApproachDir(FMath::Cos(ApproachAngle), FMath::Sin(ApproachAngle));
|
FVector2D ApproachDir(FMath::Cos(ApproachAngle), FMath::Sin(ApproachAngle));
|
||||||
|
|
||||||
// Base spawn position far from player
|
// Base spawn position far from player - increased from 2500 to MinimumSpawnDistance + 500
|
||||||
FVector BaseSpawnPos = PlayerLocation + FVector(ApproachDir.X, ApproachDir.Y, 0) * 2500.0f;
|
FVector BaseSpawnPos = PlayerLocation + FVector(ApproachDir.X, ApproachDir.Y, 0) * (MinimumSpawnDistance + 500.0f);
|
||||||
|
|
||||||
// Create formation positions
|
// Create formation positions
|
||||||
TArray<FVector> FormationPositions;
|
TArray<FVector> FormationPositions;
|
||||||
@@ -261,13 +264,16 @@ void ASpaceShooterGameMode::SpawnEnemyFormation()
|
|||||||
// Spawn enemies at formation positions
|
// Spawn enemies at formation positions
|
||||||
for (const FVector& Position : FormationPositions)
|
for (const FVector& Position : FormationPositions)
|
||||||
{
|
{
|
||||||
|
// Ensure the spawn location is far enough from the player
|
||||||
|
FVector SpawnLocation = EnsureMinimumSpawnDistance(Position, PlayerLocation);
|
||||||
|
|
||||||
FRotator SpawnRotation = FRotator::ZeroRotator;
|
FRotator SpawnRotation = FRotator::ZeroRotator;
|
||||||
FActorSpawnParameters SpawnParams;
|
FActorSpawnParameters SpawnParams;
|
||||||
SpawnParams.SpawnCollisionHandlingOverride =
|
SpawnParams.SpawnCollisionHandlingOverride =
|
||||||
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
|
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
|
||||||
|
|
||||||
AEnemySpaceship* NewEnemy = World->SpawnActor<AEnemySpaceship>(
|
AEnemySpaceship* NewEnemy = World->SpawnActor<AEnemySpaceship>(
|
||||||
EnemyClass, Position, SpawnRotation, SpawnParams);
|
EnemyClass, SpawnLocation, SpawnRotation, SpawnParams);
|
||||||
|
|
||||||
if (NewEnemy)
|
if (NewEnemy)
|
||||||
{
|
{
|
||||||
@@ -307,7 +313,11 @@ void ASpaceShooterGameMode::SpawnEnemyFlanking()
|
|||||||
float OffsetAngle = Angle + FMath::RandRange(-0.3f, 0.3f);
|
float OffsetAngle = Angle + FMath::RandRange(-0.3f, 0.3f);
|
||||||
FVector2D OffsetDir(FMath::Cos(OffsetAngle), FMath::Sin(OffsetAngle));
|
FVector2D OffsetDir(FMath::Cos(OffsetAngle), FMath::Sin(OffsetAngle));
|
||||||
|
|
||||||
FVector SpawnLocation = PlayerLocation + FVector(OffsetDir.X, OffsetDir.Y, 0) * 2000.0f;
|
// 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;
|
FRotator SpawnRotation = FRotator::ZeroRotator;
|
||||||
FActorSpawnParameters SpawnParams;
|
FActorSpawnParameters SpawnParams;
|
||||||
@@ -339,30 +349,34 @@ FVector ASpaceShooterGameMode::GetScreenEdgeSpawnLocation()
|
|||||||
FVector SpawnLocation;
|
FVector SpawnLocation;
|
||||||
float RandomPos;
|
float RandomPos;
|
||||||
|
|
||||||
|
// Increased margin to spawn farther from screen edges
|
||||||
|
float ExtendedMargin = ScreenSpawnMargin + 500.0f;
|
||||||
|
|
||||||
switch (Edge)
|
switch (Edge)
|
||||||
{
|
{
|
||||||
case 0: // Top edge
|
case 0: // Top edge
|
||||||
RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X);
|
RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X);
|
||||||
SpawnLocation = FVector(RandomPos, ScreenBounds[0].Y - ScreenSpawnMargin, PlayerLocation.Z);
|
SpawnLocation = FVector(RandomPos, ScreenBounds[0].Y - ExtendedMargin, PlayerLocation.Z);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1: // Right edge
|
case 1: // Right edge
|
||||||
RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y);
|
RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y);
|
||||||
SpawnLocation = FVector(ScreenBounds[1].X + ScreenSpawnMargin, RandomPos, PlayerLocation.Z);
|
SpawnLocation = FVector(ScreenBounds[1].X + ExtendedMargin, RandomPos, PlayerLocation.Z);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2: // Bottom edge
|
case 2: // Bottom edge
|
||||||
RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X);
|
RandomPos = FMath::RandRange(ScreenBounds[0].X, ScreenBounds[1].X);
|
||||||
SpawnLocation = FVector(RandomPos, ScreenBounds[1].Y + ScreenSpawnMargin, PlayerLocation.Z);
|
SpawnLocation = FVector(RandomPos, ScreenBounds[1].Y + ExtendedMargin, PlayerLocation.Z);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3: // Left edge
|
case 3: // Left edge
|
||||||
RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y);
|
RandomPos = FMath::RandRange(ScreenBounds[0].Y, ScreenBounds[1].Y);
|
||||||
SpawnLocation = FVector(ScreenBounds[0].X - ScreenSpawnMargin, RandomPos, PlayerLocation.Z);
|
SpawnLocation = FVector(ScreenBounds[0].X - ExtendedMargin, RandomPos, PlayerLocation.Z);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SpawnLocation;
|
// Ensure the spawn location is far enough from the player
|
||||||
|
return EnsureMinimumSpawnDistance(SpawnLocation, PlayerLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
FVector ASpaceShooterGameMode::GetSpawnZoneLocation()
|
FVector ASpaceShooterGameMode::GetSpawnZoneLocation()
|
||||||
@@ -395,6 +409,7 @@ FVector ASpaceShooterGameMode::GetSpawnZoneLocation()
|
|||||||
// Select a zone based on weight
|
// Select a zone based on weight
|
||||||
float RandomWeight = FMath::RandRange(0.0f, TotalWeight);
|
float RandomWeight = FMath::RandRange(0.0f, TotalWeight);
|
||||||
float WeightSum = 0.0f;
|
float WeightSum = 0.0f;
|
||||||
|
FVector PlayerLocation = GetPlayerLocation();
|
||||||
|
|
||||||
for (const FSpawnZone& Zone : ActiveZones)
|
for (const FSpawnZone& Zone : ActiveZones)
|
||||||
{
|
{
|
||||||
@@ -411,7 +426,10 @@ FVector ASpaceShooterGameMode::GetSpawnZoneLocation()
|
|||||||
0.0f
|
0.0f
|
||||||
);
|
);
|
||||||
|
|
||||||
return Zone.Location + SpawnOffset;
|
FVector ProposedLocation = Zone.Location + SpawnOffset;
|
||||||
|
|
||||||
|
// Ensure the spawn location is far enough from the player
|
||||||
|
return EnsureMinimumSpawnDistance(ProposedLocation, PlayerLocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,3 +523,21 @@ void ASpaceShooterGameMode::RotateTowardsPlayer(AEnemySpaceship* Enemy, const FV
|
|||||||
// Set the enemy's rotation
|
// Set the enemy's rotation
|
||||||
Enemy->SetActorRotation(NewRotation);
|
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;
|
||||||
|
}
|
||||||
@@ -55,7 +55,10 @@ protected:
|
|||||||
int32 MaxEnemies = 10;
|
int32 MaxEnemies = 10;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
float ScreenSpawnMargin = 100.0f;
|
float ScreenSpawnMargin = 300.0f; // Increased from 100.0f
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
|
float MinimumSpawnDistance = 4000.0f; // New property to ensure minimum spawn distance
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
|
||||||
TArray<FSpawnZone> SpawnZones;
|
TArray<FSpawnZone> SpawnZones;
|
||||||
@@ -93,4 +96,7 @@ private:
|
|||||||
FVector GetPlayerLocation();
|
FVector GetPlayerLocation();
|
||||||
TArray<FVector2D> GetScreenBounds();
|
TArray<FVector2D> GetScreenBounds();
|
||||||
void RotateTowardsPlayer(AEnemySpaceship* Enemy, const FVector& PlayerLocation);
|
void RotateTowardsPlayer(AEnemySpaceship* Enemy, const FVector& PlayerLocation);
|
||||||
|
|
||||||
|
// New helper method to ensure minimum spawn distance
|
||||||
|
FVector EnsureMinimumSpawnDistance(const FVector& ProposedLocation, const FVector& PlayerLocation);
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "SpaceshipPawn.h"
|
#include "SpaceshipPawn.h"
|
||||||
#include "GameFramework/SpringArmComponent.h"
|
#include "GameFramework/SpringArmComponent.h"
|
||||||
#include "Camera/CameraComponent.h"
|
#include "Camera/CameraComponent.h"
|
||||||
|
#include "Camera/CameraShakeBase.h"
|
||||||
#include "Components/StaticMeshComponent.h"
|
#include "Components/StaticMeshComponent.h"
|
||||||
#include "EnhancedInputComponent.h"
|
#include "EnhancedInputComponent.h"
|
||||||
#include "EnhancedInputSubsystems.h"
|
#include "EnhancedInputSubsystems.h"
|
||||||
@@ -59,6 +60,7 @@ ASpaceshipPawn::ASpaceshipPawn()
|
|||||||
ShipMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
|
ShipMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
|
||||||
ShipMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECollisionResponse::ECR_Ignore);
|
ShipMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECollisionResponse::ECR_Ignore);
|
||||||
ShipMesh->SetCollisionResponseToChannel(ECC_Pawn, ECollisionResponse::ECR_Ignore);
|
ShipMesh->SetCollisionResponseToChannel(ECC_Pawn, ECollisionResponse::ECR_Ignore);
|
||||||
|
ShipMesh->SetGenerateOverlapEvents(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +106,11 @@ void ASpaceshipPawn::BeginPlay()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize health and shield values
|
||||||
|
CurrentHealth = MaxHealth;
|
||||||
|
CurrentShield = MaxShield;
|
||||||
|
LastDamageTime = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASpaceshipPawn::Tick(float DeltaTime)
|
void ASpaceshipPawn::Tick(float DeltaTime)
|
||||||
@@ -201,7 +208,7 @@ void ASpaceshipPawn::UpdateArcadeMovement(float DeltaTime)
|
|||||||
|
|
||||||
// Calculate desired velocity (forward/back + strafe)
|
// Calculate desired velocity (forward/back + strafe)
|
||||||
FVector DesiredVelocity = (ForwardVector * CurrentThrottleInput * MaxSpeed) +
|
FVector DesiredVelocity = (ForwardVector * CurrentThrottleInput * MaxSpeed) +
|
||||||
(RightVector * CurrentStrafeInput * (MaxSpeed * 0.7f)); // Strafe is slightly slower
|
(RightVector * CurrentStrafeInput * (MaxSpeed * 0.95f)); // Strafe is slightly slower
|
||||||
|
|
||||||
// Smoothly interpolate current velocity to desired velocity
|
// Smoothly interpolate current velocity to desired velocity
|
||||||
if (!FMath::IsNearlyZero(CurrentThrottleInput) || !FMath::IsNearlyZero(CurrentStrafeInput))
|
if (!FMath::IsNearlyZero(CurrentThrottleInput) || !FMath::IsNearlyZero(CurrentStrafeInput))
|
||||||
@@ -395,3 +402,197 @@ void ASpaceshipPawn::ResetFire()
|
|||||||
{
|
{
|
||||||
bCanFire = true;
|
bCanFire = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float ASpaceshipPawn::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
|
||||||
|
AController* EventInstigator, AActor* DamageCauser)
|
||||||
|
{
|
||||||
|
float DamageToApply = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
|
||||||
|
|
||||||
|
// Update last damage time
|
||||||
|
LastDamageTime = GetWorld()->GetTimeSeconds();
|
||||||
|
|
||||||
|
// Stop shield recharge and clear timer
|
||||||
|
GetWorldTimerManager().ClearTimer(ShieldRechargeTimerHandle);
|
||||||
|
|
||||||
|
// Apply damage to shield first
|
||||||
|
if (CurrentShield > 0.0f)
|
||||||
|
{
|
||||||
|
if (CurrentShield >= DamageToApply)
|
||||||
|
{
|
||||||
|
// Shield absorbs all damage
|
||||||
|
CurrentShield -= DamageToApply;
|
||||||
|
DamageToApply = 0.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Shield absorbs part of the damage
|
||||||
|
DamageToApply -= CurrentShield;
|
||||||
|
CurrentShield = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply remaining damage to health
|
||||||
|
CurrentHealth -= DamageToApply;
|
||||||
|
|
||||||
|
// Debug message
|
||||||
|
if (GEngine)
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red,
|
||||||
|
FString::Printf(TEXT("Player Hit! Health: %.1f, Shield: %.1f"),
|
||||||
|
CurrentHealth, CurrentShield));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply visual damage effects
|
||||||
|
ApplyDamageFlash();
|
||||||
|
|
||||||
|
// Play impact effect
|
||||||
|
if (ImpactEffect)
|
||||||
|
{
|
||||||
|
UGameplayStatics::SpawnEmitterAttached(
|
||||||
|
ImpactEffect,
|
||||||
|
ShipMesh,
|
||||||
|
NAME_None,
|
||||||
|
GetActorLocation(),
|
||||||
|
GetActorRotation(),
|
||||||
|
EAttachLocation::KeepWorldPosition
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play impact sound
|
||||||
|
if (ImpactSound)
|
||||||
|
{
|
||||||
|
UGameplayStatics::PlaySoundAtLocation(
|
||||||
|
this,
|
||||||
|
ImpactSound,
|
||||||
|
GetActorLocation()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start shield recharge timer after delay
|
||||||
|
GetWorldTimerManager().SetTimer(
|
||||||
|
ShieldRechargeTimerHandle,
|
||||||
|
this,
|
||||||
|
&ASpaceshipPawn::StartShieldRecharge,
|
||||||
|
ShieldRechargeDelay,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for death
|
||||||
|
if (CurrentHealth <= 0.0f)
|
||||||
|
{
|
||||||
|
Die();
|
||||||
|
}
|
||||||
|
|
||||||
|
return DamageToApply;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASpaceshipPawn::ApplyDamageFlash()
|
||||||
|
{
|
||||||
|
// Apply material flash effect to indicate damage
|
||||||
|
if (ShipMesh && ShipMesh->GetMaterial(0))
|
||||||
|
{
|
||||||
|
UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(ShipMesh->GetMaterial(0), this);
|
||||||
|
if (DynamicMaterial)
|
||||||
|
{
|
||||||
|
// Assuming the material has a parameter named "FlashColor"
|
||||||
|
DynamicMaterial->SetVectorParameterValue("FlashColor", DamageFlashColor);
|
||||||
|
ShipMesh->SetMaterial(0, DynamicMaterial);
|
||||||
|
|
||||||
|
// Set timer to reset the flash
|
||||||
|
GetWorldTimerManager().SetTimer(
|
||||||
|
DamageFlashTimerHandle,
|
||||||
|
this,
|
||||||
|
&ASpaceshipPawn::ResetDamageFlash,
|
||||||
|
DamageFlashDuration,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASpaceshipPawn::ResetDamageFlash()
|
||||||
|
{
|
||||||
|
// Reset material flash effect
|
||||||
|
if (ShipMesh && ShipMesh->GetMaterial(0))
|
||||||
|
{
|
||||||
|
UMaterialInstanceDynamic* DynamicMaterial = Cast<UMaterialInstanceDynamic>(ShipMesh->GetMaterial(0));
|
||||||
|
if (DynamicMaterial)
|
||||||
|
{
|
||||||
|
// Reset the flash color (assuming transparent black means no flash)
|
||||||
|
DynamicMaterial->SetVectorParameterValue("FlashColor", FLinearColor(0.0f, 0.0f, 0.0f, 0.0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASpaceshipPawn::StartShieldRecharge()
|
||||||
|
{
|
||||||
|
// Start continuous shield recharge
|
||||||
|
GetWorldTimerManager().SetTimer(
|
||||||
|
ShieldRechargeTimerHandle,
|
||||||
|
this,
|
||||||
|
&ASpaceshipPawn::RechargeShield,
|
||||||
|
0.1f, // update every 0.1 seconds
|
||||||
|
true // looping
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASpaceshipPawn::RechargeShield()
|
||||||
|
{
|
||||||
|
// Recharge shield gradually
|
||||||
|
if (CurrentShield < MaxShield)
|
||||||
|
{
|
||||||
|
CurrentShield = FMath::Min(CurrentShield + (ShieldRechargeRate * 0.1f), MaxShield);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Shield is full, stop the timer
|
||||||
|
GetWorldTimerManager().ClearTimer(ShieldRechargeTimerHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASpaceshipPawn::Die()
|
||||||
|
{
|
||||||
|
// Play death explosion effect
|
||||||
|
UGameplayStatics::SpawnEmitterAtLocation(
|
||||||
|
GetWorld(),
|
||||||
|
ImpactEffect,
|
||||||
|
GetActorLocation(),
|
||||||
|
GetActorRotation(),
|
||||||
|
FVector(3.0f) // Larger explosion for death
|
||||||
|
);
|
||||||
|
|
||||||
|
// Play death sound
|
||||||
|
if (ImpactSound)
|
||||||
|
{
|
||||||
|
UGameplayStatics::PlaySoundAtLocation(
|
||||||
|
this,
|
||||||
|
ImpactSound,
|
||||||
|
GetActorLocation(),
|
||||||
|
2.0f // Louder for death
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the ship mesh
|
||||||
|
if (ShipMesh)
|
||||||
|
{
|
||||||
|
ShipMesh->SetVisibility(false);
|
||||||
|
ShipMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable input
|
||||||
|
APlayerController* PC = Cast<APlayerController>(GetController());
|
||||||
|
if (PC)
|
||||||
|
{
|
||||||
|
DisableInput(PC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// You could either restart the level after a delay or show a game over screen here
|
||||||
|
// For example:
|
||||||
|
// GetWorldTimerManager().SetTimer(RestartTimerHandle, this, &ASpaceshipPawn::RestartLevel, 3.0f, false);
|
||||||
|
|
||||||
|
// For now, just log the death
|
||||||
|
if (GEngine)
|
||||||
|
{
|
||||||
|
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("PLAYER DIED!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,18 @@ public:
|
|||||||
virtual void Tick(float DeltaTime) override;
|
virtual void Tick(float DeltaTime) override;
|
||||||
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
||||||
|
|
||||||
|
// Override TakeDamage to handle player damage
|
||||||
|
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent,
|
||||||
|
class AController* EventInstigator, AActor* DamageCauser) override;
|
||||||
|
|
||||||
|
// Get current health percentage
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Combat")
|
||||||
|
float GetHealthPercentage() const { return CurrentHealth / MaxHealth; }
|
||||||
|
|
||||||
|
// Get current shield percentage
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Combat")
|
||||||
|
float GetShieldPercentage() const { return CurrentShield / MaxShield; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
@@ -48,12 +60,20 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
|
||||||
class UInputAction* MouseLookAction;
|
class UInputAction* MouseLookAction;
|
||||||
|
|
||||||
|
// Add a strafe input to allow lateral movement
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
|
||||||
|
class UInputAction* StrafeAction;
|
||||||
|
|
||||||
|
// Input action for shooting
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
|
||||||
|
class UInputAction* ShootAction;
|
||||||
|
|
||||||
// Movement Parameters
|
// Movement Parameters
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
||||||
EShipMovementMode MovementMode = EShipMovementMode::Arcade;
|
EShipMovementMode MovementMode = EShipMovementMode::Arcade;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
||||||
float MaxSpeed = 2000.0f;
|
float MaxSpeed = 3000.0f;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
||||||
float Acceleration = 2000.0f;
|
float Acceleration = 2000.0f;
|
||||||
@@ -86,10 +106,6 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
|
||||||
float AutoBrakeStrength = 3.0f;
|
float AutoBrakeStrength = 3.0f;
|
||||||
|
|
||||||
// Add a strafe input to allow lateral movement
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
|
|
||||||
class UInputAction* StrafeAction;
|
|
||||||
|
|
||||||
// Shooting properties
|
// Shooting properties
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
TSubclassOf<class ASpaceshipProjectile> ProjectileClass;
|
TSubclassOf<class ASpaceshipProjectile> ProjectileClass;
|
||||||
@@ -100,10 +116,6 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
USceneComponent* ProjectileSpawnPoint;
|
USceneComponent* ProjectileSpawnPoint;
|
||||||
|
|
||||||
// Input action for shooting
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
|
|
||||||
class UInputAction* ShootAction;
|
|
||||||
|
|
||||||
UPROPERTY(EditDefaultsOnly, Category = "UI")
|
UPROPERTY(EditDefaultsOnly, Category = "UI")
|
||||||
TSubclassOf<UUserWidget> CrosshairWidgetClass;
|
TSubclassOf<UUserWidget> CrosshairWidgetClass;
|
||||||
|
|
||||||
@@ -115,6 +127,43 @@ protected:
|
|||||||
void HandleStrafeInput(const FInputActionValue& Value);
|
void HandleStrafeInput(const FInputActionValue& Value);
|
||||||
void HandleMouseLook(const FInputActionValue& Value);
|
void HandleMouseLook(const FInputActionValue& Value);
|
||||||
|
|
||||||
|
// Health properties
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
float MaxHealth = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
float CurrentHealth = 100.0f;
|
||||||
|
|
||||||
|
// Shield properties
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
float MaxShield = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
float CurrentShield = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
float ShieldRechargeRate = 5.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
|
||||||
|
float ShieldRechargeDelay = 3.0f;
|
||||||
|
|
||||||
|
// Impact effects
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||||
|
UParticleSystem* ImpactEffect;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||||
|
class USoundBase* ImpactSound;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||||
|
class UCameraShakeBase* DamageShake;
|
||||||
|
|
||||||
|
// Damage flash effect for the ship
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||||
|
float DamageFlashDuration = 0.2f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects")
|
||||||
|
FLinearColor DamageFlashColor = FLinearColor(1.0f, 0.0f, 0.0f, 0.5f);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Movement state
|
// Movement state
|
||||||
float CurrentThrottleInput;
|
float CurrentThrottleInput;
|
||||||
@@ -143,4 +192,17 @@ private:
|
|||||||
void UpdateAssistedMovement(float DeltaTime);
|
void UpdateAssistedMovement(float DeltaTime);
|
||||||
void UpdateRealisticMovement(float DeltaTime);
|
void UpdateRealisticMovement(float DeltaTime);
|
||||||
void UpdateShipRotation(float DeltaTime);
|
void UpdateShipRotation(float DeltaTime);
|
||||||
|
|
||||||
|
// Damage handling
|
||||||
|
void ApplyDamageFlash();
|
||||||
|
void ResetDamageFlash();
|
||||||
|
|
||||||
|
FTimerHandle ShieldRechargeTimerHandle;
|
||||||
|
FTimerHandle DamageFlashTimerHandle;
|
||||||
|
float LastDamageTime;
|
||||||
|
|
||||||
|
void StartShieldRecharge();
|
||||||
|
void RechargeShield();
|
||||||
|
bool IsDead() const { return CurrentHealth <= 0.0f; }
|
||||||
|
void Die();
|
||||||
};
|
};
|
||||||
@@ -22,7 +22,7 @@ protected:
|
|||||||
class UProjectileMovementComponent* ProjectileMovement;
|
class UProjectileMovementComponent* ProjectileMovement;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
|
||||||
float ProjectileSpeed = 3000.0f;
|
float ProjectileSpeed = 3500.0f;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
|
||||||
float DamageAmount = 20.0f;
|
float DamageAmount = 20.0f;
|
||||||
|
|||||||
Reference in New Issue
Block a user