#include "SpaceshipPawn.h" #include "GameFramework/SpringArmComponent.h" #include "Camera/CameraComponent.h" #include "Components/StaticMeshComponent.h" #include "EnhancedInputComponent.h" #include "EnhancedInputSubsystems.h" #include "SpaceshipProjectile.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/GameUserSettings.h" ASpaceshipPawn::ASpaceshipPawn() { PrimaryActorTick.bCanEverTick = true; // Create ship mesh ShipMesh = CreateDefaultSubobject(TEXT("ShipMesh")); RootComponent = ShipMesh; ShipMesh->SetSimulatePhysics(false); ShipMesh->SetEnableGravity(false); // Create projectile spawn point ProjectileSpawnPoint = CreateDefaultSubobject(TEXT("ProjectileSpawnPoint")); ProjectileSpawnPoint->SetupAttachment(ShipMesh); // Create camera spring arm CameraSpringArm = CreateDefaultSubobject(TEXT("CameraSpringArm")); CameraSpringArm->SetupAttachment(RootComponent); CameraSpringArm->TargetArmLength = 400.0f; CameraSpringArm->bEnableCameraLag = true; CameraSpringArm->CameraLagSpeed = 10.0f; CameraSpringArm->bEnableCameraRotationLag = true; CameraSpringArm->CameraRotationLagSpeed = 10.0f; CameraSpringArm->CameraLagMaxDistance = 7.0f; CameraSpringArm->bUsePawnControlRotation = false; // Add this line CameraSpringArm->bInheritPitch = true; // Add this line CameraSpringArm->bInheritYaw = true; // Add this line CameraSpringArm->bInheritRoll = false; // Add this line // Create camera Camera = CreateDefaultSubobject(TEXT("Camera")); Camera->SetupAttachment(CameraSpringArm, USpringArmComponent::SocketName); // Initialize movement variables CurrentThrust = 0.0f; TargetThrust = 0.0f; bThrottlePressed = false; CurrentVelocity = FVector::ZeroVector; CurrentPitch = 0.0f; 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(Controller); if (PlayerControllerRef) { // Setup input mapping context if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem(PlayerControllerRef->GetLocalPlayer())) { Subsystem->AddMappingContext(DefaultMappingContext, 0); } // Setup mouse capture and fullscreen PlayerControllerRef->SetShowMouseCursor(false); PlayerControllerRef->SetInputMode(FInputModeGameOnly()); // Request fullscreen mode using GameUserSettings UGameUserSettings* GameUserSettings = UGameUserSettings::GetGameUserSettings(); if (GameUserSettings) { GameUserSettings->SetFullscreenMode(EWindowMode::Fullscreen); GameUserSettings->SetScreenResolution(FIntPoint(1920, 1080)); // Adjust the resolution if needed GameUserSettings->ApplySettings(false); } } } void ASpaceshipPawn::Tick(float DeltaTime) { Super::Tick(DeltaTime); // Debug info if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Yellow, FString::Printf(TEXT("Thrust: %.2f"), CurrentThrust)); GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Green, FString::Printf(TEXT("Velocity: %.2f"), CurrentVelocity.Size())); } // Smooth mouse movement MouseDeltaSmoothed = FMath::Vector2DInterpTo( MouseDeltaSmoothed, LastMouseDelta, DeltaTime, MouseSmoothingSpeed ); // Update rotation based on smoothed mouse movement CurrentYaw += MouseDeltaSmoothed.X * MouseSensitivity * DeltaTime * 60.0f; // Multiply by 60 to normalize for frame rate CurrentPitch = FMath::ClampAngle( CurrentPitch + (MouseDeltaSmoothed.Y * MouseSensitivity * DeltaTime * 60.0f), -85.0f, 85.0f ); // Set target rotation TargetRotation = FRotator(CurrentPitch, CurrentYaw, 0.0f); // Smoothly interpolate to target rotation using quaternions for better interpolation FQuat CurrentQuat = GetActorQuat(); FQuat TargetQuat = TargetRotation.Quaternion(); 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) { FRotator SpringArmRotation = CameraSpringArm->GetComponentRotation(); FRotator TargetSpringArmRotation = NewQuat.Rotator(); FRotator NewSpringArmRotation = FMath::RInterpTo( SpringArmRotation, TargetSpringArmRotation, DeltaTime, RotationSpeed ); CameraSpringArm->SetWorldRotation(NewSpringArmRotation); } // Reset mouse delta for next frame LastMouseDelta = FVector2D::ZeroVector; // Debug info if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Yellow, FString::Printf(TEXT("Smoothed Mouse Delta: X=%.2f Y=%.2f"), MouseDeltaSmoothed.X, MouseDeltaSmoothed.Y)); GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Green, FString::Printf(TEXT("Current Rotation: P=%.2f Y=%.2f"), CurrentPitch, CurrentYaw)); } } void ASpaceshipPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked(PlayerInputComponent)) { // Bind both Hold and Released events for throttle EnhancedInputComponent->BindAction(ThrottleAction, ETriggerEvent::Started, this, &ASpaceshipPawn::HandleThrottleStarted); EnhancedInputComponent->BindAction(ThrottleAction, ETriggerEvent::Completed, this, &ASpaceshipPawn::HandleThrottleReleased); // Bind mouse control EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &ASpaceshipPawn::HandleMouseLook); // Add shooting binding EnhancedInputComponent->BindAction(ShootAction, ETriggerEvent::Triggered, this, &ASpaceshipPawn::HandleShoot); } } // Split the throttle handling into two functions void ASpaceshipPawn::HandleThrottleStarted(const FInputActionValue& Value) { const float ThrottleValue = Value.Get(); bThrottlePressed = true; TargetThrust = ThrottleValue * MaxThrust; } void ASpaceshipPawn::HandleThrottleReleased(const FInputActionValue& Value) { bThrottlePressed = false; } void ASpaceshipPawn::HandleMouseLook(const FInputActionValue& Value) { const FVector2D MouseDelta = Value.Get(); // Smoothly interpolate mouse delta LastMouseDelta = MouseDelta; } 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 = ProjectileSpawnPoint->GetComponentLocation(); FRotator SpawnRotation = GetActorRotation(); FActorSpawnParameters SpawnParams; SpawnParams.Owner = this; SpawnParams.Instigator = GetInstigator(); // Spawn the projectile ASpaceshipProjectile* Projectile = World->SpawnActor( 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; }