5 Commits

15 changed files with 368 additions and 144 deletions

View File

@@ -148,3 +148,61 @@ ConnectionType=USBOnly
bUseManualIPAddress=False
ManualIPAddress=
[/Script/Engine.CollisionProfile]
-Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False)
-Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
-Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
-Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
-Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
-Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False)
-Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False)
-Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False)
-Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic",Response=ECR_Block),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False)
-Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False)
-Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False)
-Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False)
-Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False)
-Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False)
-Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False)
-Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False)
-Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False)
-Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Block),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
+Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision")
+Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ")
+Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ")
+Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ")
+Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ")
+Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.")
+Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ")
+Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ")
+Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic"),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.")
+Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.")
+Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors")
+Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors")
+Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.")
+Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.")
+Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.")
+Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.")
+Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.")
+Profiles=(Name="UI",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ")
+Profiles=(Name="Projectile",CollisionEnabled=QueryOnly,bCanModify=True,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Player",Response=ECR_Ignore)),HelpMessage="Needs description")
+DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Block,bTraceType=False,bStaticObject=False,Name="Player")
-ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall")
-ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn")
-ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic")
-ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor")
-ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic")
+ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall")
+ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn")
+ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic")
+ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor")
+ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic")
-CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic")
-CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic")
-CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")
-CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn")
+CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic")
+CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic")
+CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")
+CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn")

Binary file not shown.

BIN
Content/Input/IA_Shoot.uasset LFS Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -7,9 +7,8 @@ A spaceship simulator game developed in Unreal Engine 5, focusing on realistic p
- Realistic zero-gravity space flight physics
- Comprehensive power management system
- Dynamic damage and shield mechanics
- Advanced navigation and autopilot capabilities
- Detailed ship systems simulation
- Interactive cockpit and HUD
- Interactive HUD
## Prerequisites
- Unreal Engine 5.2 or higher
@@ -38,13 +37,13 @@ git lfs pull
### Project Structure
```
├── Config/ # Engine configuration
├── Config/ # Engine configuration
├── Content/
│ ├── Core/ # Base game systems
│ ├── Ships/ # Ship-related assets
│ ├── Environment/ # Space environment
│ ├── UI/ # HUD and menus
│ └── VFX/ # Visual effects
│ └── VFX/ # Visual effects
├── Source/ # C++ source code
└── Documentation/ # Additional documentation
```
@@ -98,8 +97,5 @@ git checkout -b feature/your-feature-name
- Aiden Correya: Core Systems Developer
- Shashank Kumar: Content and Design Developer
## License
- To be decided
## Acknowledgments
- Unreal Engine
- Epic Games (Unreal Engine)

View File

@@ -1,24 +1,33 @@
#include "EnemySpaceship.h"
#include "SpaceshipPawn.h"
#include "SpaceshipProjectile.h"
#include "Kismet/GameplayStatics.h"
AEnemySpaceship::AEnemySpaceship()
{
PrimaryActorTick.bCanEverTick = true;
ShipMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ShipMesh"));
RootComponent = ShipMesh;
// Create and setup the enemy mesh
EnemyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EnemyMesh"));
RootComponent = EnemyMesh;
CurrentHealth = MaxHealth;
// Disable gravity and physics simulation
EnemyMesh->SetSimulatePhysics(false);
EnemyMesh->SetEnableGravity(false);
// Set up collision for the enemy mesh
EnemyMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
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->SetGenerateOverlapEvents(true);
}
void AEnemySpaceship::BeginPlay()
{
Super::BeginPlay();
PlayerPawn = Cast<ASpaceshipPawn>(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));
GetWorldTimerManager().SetTimer(FireTimerHandle, this, &AEnemySpaceship::FireAtPlayer, FireRate, true);
// Find the player pawn
PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
}
void AEnemySpaceship::Tick(float DeltaTime)
@@ -27,66 +36,43 @@ void AEnemySpaceship::Tick(float DeltaTime)
if (PlayerPawn)
{
// Calculate distance to player
float DistanceToPlayer = FVector::Dist(GetActorLocation(), PlayerPawn->GetActorLocation());
// Move towards the player
FVector Direction = (PlayerPawn->GetActorLocation() - GetActorLocation()).GetSafeNormal();
FVector NewLocation = GetActorLocation() + Direction * MovementSpeed * DeltaTime;
SetActorLocation(NewLocation);
if (DistanceToPlayer <= DetectionRange)
{
// Look at player
FVector Direction = PlayerPawn->GetActorLocation() - GetActorLocation();
FRotator NewRotation = Direction.Rotation();
SetActorRotation(FMath::RInterpTo(GetActorRotation(), NewRotation, DeltaTime, 2.0f));
// Move towards player while maintaining some distance
float TargetDistance = 1000.0f;
if (DistanceToPlayer > TargetDistance)
{
FVector NewLocation = GetActorLocation() + (Direction.GetSafeNormal() * MovementSpeed * DeltaTime);
SetActorLocation(NewLocation);
}
else if (DistanceToPlayer < TargetDistance - 100.0f)
{
FVector NewLocation = GetActorLocation() - (Direction.GetSafeNormal() * MovementSpeed * DeltaTime);
SetActorLocation(NewLocation);
}
}
// Face towards the player
FRotator NewRotation = Direction.Rotation();
SetActorRotation(NewRotation);
}
}
void AEnemySpaceship::FireAtPlayer()
{
if (PlayerPawn && FVector::Dist(GetActorLocation(), PlayerPawn->GetActorLocation()) <= DetectionRange)
{
UWorld* World = GetWorld();
if (World)
{
FVector SpawnLocation = GetActorLocation() + (GetActorForwardVector() * 100.0f);
FRotator SpawnRotation = GetActorRotation();
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = GetInstigator();
ASpaceshipProjectile* Projectile = World->SpawnActor<ASpaceshipProjectile>(
ASpaceshipProjectile::StaticClass(),
SpawnLocation,
SpawnRotation,
SpawnParams
);
}
}
}
float AEnemySpaceship::TakeDamage(float Damage, FDamageEvent const& DamageEvent,
float AEnemySpaceship::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
AController* EventInstigator, AActor* DamageCauser)
{
float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);
float DamageToApply = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
CurrentHealth -= ActualDamage;
if (CurrentHealth <= 0)
CurrentHealth -= DamageToApply;
// Debug message
if (GEngine)
{
// Destroy the enemy ship
Destroy();
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red,
FString::Printf(TEXT("Enemy Health: %f"), CurrentHealth));
}
return ActualDamage;
if (CurrentHealth <= 0)
{
Die();
}
return DamageToApply;
}
void AEnemySpaceship::Die()
{
// Add any death effects here
// Destroy the enemy
Destroy();
}

View File

@@ -1,41 +1,40 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/Actor.h"
#include "EnemySpaceship.generated.h"
UCLASS()
class MYPROJECT3_API AEnemySpaceship : public APawn
class MYPROJECT3_API AEnemySpaceship : public AActor
{
GENERATED_BODY()
public:
AEnemySpaceship();
virtual void Tick(float DeltaTime) override;
protected:
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components")
class UStaticMeshComponent* ShipMesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
float DetectionRange = 1500.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
float FireRate = 2.0f;
UStaticMeshComponent* EnemyMesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float MovementSpeed = 500.0f;
float MovementSpeed = 300.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
float MaxHealth = 100.0f;
float CurrentHealth;
FTimerHandle FireTimerHandle;
class ASpaceshipPawn* PlayerPawn;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
float CurrentHealth = 100.0f;
void FireAtPlayer();
virtual float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent,
AController* EventInstigator, AActor* DamageCauser) override;
public:
virtual void Tick(float DeltaTime) override;
// Override TakeDamage to handle damage taken
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent,
class AController* EventInstigator, AActor* DamageCauser) override;
private:
AActor* PlayerPawn;
void Die();
};

View File

@@ -12,6 +12,13 @@ ASpaceShooterGameMode::ASpaceShooterGameMode()
DefaultPawnClass = PlayerPawnBPClass.Class;
}
// Find enemy blueprint class
static ConstructorHelpers::FClassFinder<AEnemySpaceship> EnemyBPClass(TEXT("/Game/Blueprints/BP_EnemySpaceship"));
if (EnemyBPClass.Class != nullptr)
{
EnemyClass = EnemyBPClass.Class;
}
// Enable Tick()
PrimaryActorTick.bCanEverTick = true;
@@ -52,14 +59,15 @@ void ASpaceShooterGameMode::SpawnEnemy()
if (FoundEnemies.Num() < MaxEnemies)
{
UWorld* World = GetWorld();
if (World)
if (World && EnemyClass)
{
FVector SpawnLocation = GetRandomSpawnLocation();
FRotator SpawnRotation = FRotator::ZeroRotator;
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
World->SpawnActor<AEnemySpaceship>(AEnemySpaceship::StaticClass(), SpawnLocation,
// Spawn using the Blueprint class instead of the C++ class directly
AEnemySpaceship* NewEnemy = World->SpawnActor<AEnemySpaceship>(EnemyClass, SpawnLocation,
SpawnRotation, SpawnParams);
}
}
@@ -75,10 +83,13 @@ FVector ASpaceShooterGameMode::GetRandomSpawnLocation()
// Generate random angle
float Angle = FMath::RandRange(0.0f, 2.0f * PI);
// Generate random radius between min and max
float Radius = FMath::RandRange(MinSpawnRadius, MaxSpawnRadius);
// Calculate spawn position in a circle around the player
FVector SpawnLocation;
SpawnLocation.X = PlayerLocation.X + SpawnRadius * FMath::Cos(Angle);
SpawnLocation.Y = PlayerLocation.Y + SpawnRadius * FMath::Sin(Angle);
SpawnLocation.X = PlayerLocation.X + Radius * FMath::Cos(Angle);
SpawnLocation.Y = PlayerLocation.Y + Radius * FMath::Sin(Angle);
SpawnLocation.Z = PlayerLocation.Z;
return SpawnLocation;

View File

@@ -17,13 +17,19 @@ public:
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
float EnemySpawnInterval = 3.0f;
TSubclassOf<class AEnemySpaceship> EnemyClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
float SpawnRadius = 2000.0f;
float EnemySpawnInterval = 2.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
int32 MaxEnemies = 5;
int32 MaxEnemies = 10;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
float MinSpawnRadius = 1000.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
float MaxSpawnRadius = 2000.0f;
private:
FTimerHandle EnemySpawnTimer;

View File

@@ -19,6 +19,10 @@ ASpaceshipPawn::ASpaceshipPawn()
ShipMesh->SetSimulatePhysics(false);
ShipMesh->SetEnableGravity(false);
// Create projectile spawn point
ProjectileSpawnPoint = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSpawnPoint"));
ProjectileSpawnPoint->SetupAttachment(ShipMesh);
// Create camera spring arm
CameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
CameraSpringArm->SetupAttachment(RootComponent);
@@ -46,12 +50,40 @@ ASpaceshipPawn::ASpaceshipPawn()
CurrentYaw = 0.0f;
TargetRotation = FRotator::ZeroRotator;
LastMouseDelta = FVector2D::ZeroVector;
if (ShipMesh)
{
ShipMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
ShipMesh->SetCollisionObjectType(ECC_GameTraceChannel1); // This is the "Player" channel
ShipMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
ShipMesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECollisionResponse::ECR_Ignore);
ShipMesh->SetCollisionResponseToChannel(ECC_Pawn, ECollisionResponse::ECR_Ignore);
}
}
void ASpaceshipPawn::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
PlayerControllerRef = Cast<APlayerController>(Controller);
@@ -82,30 +114,6 @@ void ASpaceshipPawn::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Handle thrust
if (!bThrottlePressed)
{
CurrentThrust = FMath::FInterpTo(CurrentThrust, 0.0f, DeltaTime, ThrustDeceleration);
}
else
{
CurrentThrust = FMath::FInterpTo(CurrentThrust, TargetThrust, DeltaTime, ThrustAcceleration);
}
// Update movement based on ship's forward vector
FVector ThrustDirection = GetActorForwardVector();
FVector ThrustForce = ThrustDirection * CurrentThrust;
// Apply drag
FVector DragForce = -CurrentVelocity * DragCoefficient;
// Update velocity
CurrentVelocity += (ThrustForce + DragForce) * DeltaTime;
// Update position
FVector NewLocation = GetActorLocation() + (CurrentVelocity * DeltaTime);
SetActorLocation(NewLocation, true);
// Debug info
if (GEngine)
{
@@ -140,6 +148,67 @@ void ASpaceshipPawn::Tick(float DeltaTime)
FQuat NewQuat = FQuat::Slerp(CurrentQuat, TargetQuat, RotationSpeed * DeltaTime);
SetActorRotation(NewQuat);
// Get the current forward vector
FVector CurrentDirection = GetActorForwardVector();
float CurrentSpeed = CurrentVelocity.Size();
// Handle thrust and velocity direction
if (!bThrottlePressed)
{
// Decelerate when not thrusting
CurrentThrust = FMath::FInterpTo(CurrentThrust, 0.0f, DeltaTime, ThrustDeceleration);
bWasThrottlePressed = false;
// Maintain current velocity direction when not thrusting
DesiredVelocityDirection = CurrentVelocity.GetSafeNormal();
}
else
{
// Accelerate when thrusting
CurrentThrust = FMath::FInterpConstantTo(CurrentThrust, TargetThrust, DeltaTime, ThrustAcceleration);
if (!bWasThrottlePressed)
{
// Just started thrusting - blend between current and new direction
if (!CurrentVelocity.IsNearlyZero())
{
// Blend between current velocity direction and new direction
FVector CurrentDir = CurrentVelocity.GetSafeNormal();
DesiredVelocityDirection = FMath::Lerp(CurrentDir, CurrentDirection, 1.0f - DirectionalInertia);
DesiredVelocityDirection.Normalize();
}
else
{
// If nearly stationary, use new direction
DesiredVelocityDirection = CurrentDirection;
}
}
else
{
// Continuously blend towards current facing direction while thrusting
DesiredVelocityDirection = FMath::VInterpNormalRotationTo(
DesiredVelocityDirection,
CurrentDirection,
DeltaTime,
VelocityAlignmentSpeed
);
}
bWasThrottlePressed = true;
}
// Calculate thrust force
FVector ThrustForce = CurrentDirection * CurrentThrust;
// Apply drag
FVector DragForce = -CurrentVelocity * DragCoefficient;
// Update velocity with forces
CurrentVelocity += (ThrustForce + DragForce) * DeltaTime;
// Update position
FVector NewLocation = GetActorLocation() + (CurrentVelocity * DeltaTime);
SetActorLocation(NewLocation, true);
// Update spring arm rotation to match ship with smooth interpolation
if (CameraSpringArm)
{
@@ -180,8 +249,8 @@ void ASpaceshipPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputCompo
// Bind mouse control
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &ASpaceshipPawn::HandleMouseLook);
// Bind fire action
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Triggered, this, &ASpaceshipPawn::HandleFire);
// Add shooting binding
EnhancedInputComponent->BindAction(ShootAction, ETriggerEvent::Triggered, this, &ASpaceshipPawn::HandleShoot);
}
}
@@ -206,27 +275,64 @@ void ASpaceshipPawn::HandleMouseLook(const FInputActionValue& Value)
LastMouseDelta = MouseDelta;
}
void ASpaceshipPawn::HandleFire(const FInputActionValue& Value)
void ASpaceshipPawn::HandleShoot(const FInputActionValue& Value)
{
// Debug message
if (GEngine)
GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Yellow, TEXT("HandleShoot Called"));
if (bCanFire)
{
Fire();
}
}
void ASpaceshipPawn::Fire()
{
// Debug messages
if (!ProjectileClass)
{
if (GEngine)
GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Red, TEXT("ProjectileClass not set!"));
return;
}
UWorld* World = GetWorld();
if (World)
{
FVector SpawnLocation = ShipMesh->GetSocketLocation(TEXT("ProjectileSpawn"));
if (SpawnLocation == FVector::ZeroVector)
{
SpawnLocation = GetActorLocation() + (GetActorForwardVector() * 100.0f);
}
FVector SpawnLocation = ProjectileSpawnPoint->GetComponentLocation();
FRotator SpawnRotation = GetActorRotation();
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = GetInstigator();
World->SpawnActor<ASpaceshipProjectile>(
ASpaceshipProjectile::StaticClass(),
// Spawn the projectile
ASpaceshipProjectile* Projectile = World->SpawnActor<ASpaceshipProjectile>(
ProjectileClass,
SpawnLocation,
SpawnRotation,
SpawnParams
);
if (Projectile)
{
if (GEngine)
GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Green, TEXT("Projectile Spawned"));
}
else
{
if (GEngine)
GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Red, TEXT("Failed to spawn projectile"));
}
// Start fire rate timer
bCanFire = false;
GetWorldTimerManager().SetTimer(FireTimerHandle, this, &ASpaceshipPawn::ResetFire, FireRate, false);
}
}
void ASpaceshipPawn::ResetFire()
{
bCanFire = true;
}

View File

@@ -38,9 +38,6 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* MouseLookAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* FireAction;
// Movement Parameters
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float MaxThrust = 2000.0f;
@@ -60,11 +57,31 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float DragCoefficient = 0.05f;
// How quickly velocity aligns with new direction
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement", meta = (ClampMin = "0.0", ClampMax = "10.0"))
float VelocityAlignmentSpeed = 4.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement", meta = (ClampMin = "0.0", ClampMax = "1.0", ToolTip = "How much of the current velocity is maintained when changing direction (0 = none, 1 = full)"))
float DirectionalInertia = 0.3f;
// Shooting properties
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
TSubclassOf<class ASpaceshipProjectile> ProjectileClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
float FireRate = 0.2f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
USceneComponent* ProjectileSpawnPoint;
// Input action for shooting
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* ShootAction;
// Input functions
void HandleThrottleStarted(const FInputActionValue& Value);
void HandleThrottleReleased(const FInputActionValue& Value);
void HandleMouseLook(const FInputActionValue& Value);
void HandleFire(const FInputActionValue& Value);
private:
// Movement state
@@ -81,4 +98,14 @@ private:
FVector2D MouseDeltaSmoothed;
FVector2D LastMouseDelta;
float MouseSmoothingSpeed = 10.0f;
bool bWasThrottlePressed;
FVector DesiredVelocityDirection;
FTimerHandle FireTimerHandle;
bool bCanFire = true;
void HandleShoot(const FInputActionValue& Value);
void Fire();
void ResetFire();
};

View File

@@ -1,6 +1,7 @@
#include "SpaceshipProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/StaticMeshComponent.h"
#include "EnemySpaceship.h"
ASpaceshipProjectile::ASpaceshipProjectile()
{
@@ -12,6 +13,17 @@ ASpaceshipProjectile::ASpaceshipProjectile()
ProjectileMesh->SetCollisionProfileName(TEXT("Projectile"));
ProjectileMesh->OnComponentHit.AddDynamic(this, &ASpaceshipProjectile::OnHit);
// Set up collision
ProjectileMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
ProjectileMesh->SetCollisionObjectType(ECC_WorldDynamic);
ProjectileMesh->SetCollisionResponseToAllChannels(ECR_Ignore);
ProjectileMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block); // Block pawns (enemies)
ProjectileMesh->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block); // Block world static
ProjectileMesh->SetGenerateOverlapEvents(true);
// Bind hit event
ProjectileMesh->OnComponentHit.AddDynamic(this, &ASpaceshipProjectile::OnHit);
// Create and setup projectile movement
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovement"));
ProjectileMovement->UpdatedComponent = ProjectileMesh;
@@ -32,12 +44,32 @@ void ASpaceshipProjectile::BeginPlay()
void ASpaceshipProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
FVector NormalImpulse, const FHitResult& Hit)
{
if (OtherActor && OtherActor != this)
// Debug message to verify hit detection
if (GEngine)
{
// Apply damage to the hit actor (if it implements damage interface)
FDamageEvent DamageEvent;
OtherActor->TakeDamage(DamageAmount, DamageEvent, nullptr, this);
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Yellow,
FString::Printf(TEXT("Projectile hit: %s"), *OtherActor->GetName()));
}
if (OtherActor && OtherActor != this)
{
// Check if we hit an enemy
AEnemySpaceship* Enemy = Cast<AEnemySpaceship>(OtherActor);
if (Enemy)
{
// Apply damage
FDamageEvent DamageEvent;
Enemy->TakeDamage(DamageAmount, DamageEvent, nullptr, this);
// Debug message for damage
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red,
FString::Printf(TEXT("Dealt %f damage to enemy"), DamageAmount));
}
}
}
// Destroy the projectile
Destroy();
}