#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" #include "Blueprint/UserWidget.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; CameraSpringArm->bInheritPitch = true; CameraSpringArm->bInheritYaw = true; CameraSpringArm->bInheritRoll = false; // Create camera Camera = CreateDefaultSubobject(TEXT("Camera")); Camera->SetupAttachment(CameraSpringArm, USpringArmComponent::SocketName); // Initialize movement variables CurrentThrottleInput = 0.0f; CurrentStrafeInput = 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(); // 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); } } // Create and add crosshair widget to viewport if (CrosshairWidgetClass) { APlayerController* PlayerController = Cast(GetController()); if (PlayerController) { CrosshairWidget = CreateWidget(PlayerController, CrosshairWidgetClass); if (CrosshairWidget) { CrosshairWidget->AddToViewport(); } } } } void ASpaceshipPawn::Tick(float DeltaTime) { Super::Tick(DeltaTime); // Update ship rotation first (common to all movement modes) UpdateShipRotation(DeltaTime); // Handle movement based on the selected movement mode switch (MovementMode) { case EShipMovementMode::Arcade: UpdateArcadeMovement(DeltaTime); break; case EShipMovementMode::Assisted: UpdateAssistedMovement(DeltaTime); break; case EShipMovementMode::Realistic: UpdateRealisticMovement(DeltaTime); break; } // Update position FVector NewLocation = GetActorLocation() + (CurrentVelocity * DeltaTime); SetActorLocation(NewLocation, true); // Reset mouse delta for next frame LastMouseDelta = FVector2D::ZeroVector; // Debug info if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Yellow, FString::Printf(TEXT("Mode: %s | Speed: %.1f"), MovementMode == EShipMovementMode::Arcade ? TEXT("Arcade") : MovementMode == EShipMovementMode::Assisted ? TEXT("Assisted") : TEXT("Realistic"), CurrentVelocity.Size())); GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Green, FString::Printf(TEXT("Throttle: %.2f | Strafe: %.2f"), CurrentThrottleInput, CurrentStrafeInput)); } } void ASpaceshipPawn::UpdateShipRotation(float DeltaTime) { // Smooth mouse movement MouseDeltaSmoothed = FMath::Vector2DInterpTo( MouseDeltaSmoothed, LastMouseDelta, DeltaTime, MouseSmoothingSpeed ); // Update rotation based on smoothed mouse movement CurrentYaw += MouseDeltaSmoothed.X * MouseSensitivity * DeltaTime * 60.0f; 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); // 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); } } void ASpaceshipPawn::UpdateArcadeMovement(float DeltaTime) { // In arcade mode, the ship moves exactly where it's pointed with minimal inertia FVector ForwardVector = GetActorForwardVector(); FVector RightVector = GetActorRightVector(); // Calculate desired velocity (forward/back + strafe) FVector DesiredVelocity = (ForwardVector * CurrentThrottleInput * MaxSpeed) + (RightVector * CurrentStrafeInput * (MaxSpeed * 0.7f)); // Strafe is slightly slower // Smoothly interpolate current velocity to desired velocity if (!FMath::IsNearlyZero(CurrentThrottleInput) || !FMath::IsNearlyZero(CurrentStrafeInput)) { // Accelerating CurrentVelocity = FMath::VInterpTo( CurrentVelocity, DesiredVelocity, DeltaTime, Acceleration / MaxSpeed ); } else if (bAutoBrakeEnabled) { // Auto-braking when no input CurrentVelocity = FMath::VInterpTo( CurrentVelocity, FVector::ZeroVector, DeltaTime, AutoBrakeStrength ); } else { // Gradual deceleration float Speed = CurrentVelocity.Size(); if (Speed > 0) { Speed = FMath::FInterpTo(Speed, 0, DeltaTime, Deceleration / MaxSpeed); CurrentVelocity = CurrentVelocity.GetSafeNormal() * Speed; } } } void ASpaceshipPawn::UpdateAssistedMovement(float DeltaTime) { // Assisted mode - a middle ground with some inertia but stabilization assistance FVector ForwardVector = GetActorForwardVector(); FVector RightVector = GetActorRightVector(); // Calculate thrust force FVector ThrustForce = (ForwardVector * CurrentThrottleInput * Acceleration) + (RightVector * CurrentStrafeInput * (Acceleration * 0.7f)); // Apply thrust to velocity CurrentVelocity += ThrustForce * DeltaTime; // Limit max speed float CurrentSpeed = CurrentVelocity.Size(); if (CurrentSpeed > MaxSpeed) { CurrentVelocity = CurrentVelocity.GetSafeNormal() * MaxSpeed; } // Apply auto-stabilization (gradual slowdown) when not thrusting if (FMath::IsNearlyZero(CurrentThrottleInput) && FMath::IsNearlyZero(CurrentStrafeInput) && bAutoBrakeEnabled) { CurrentVelocity = FMath::VInterpTo( CurrentVelocity, FVector::ZeroVector, DeltaTime, StabilizationSpeed ); } } void ASpaceshipPawn::UpdateRealisticMovement(float DeltaTime) { // This is the original physics-based movement with momentum FVector CurrentDirection = GetActorForwardVector(); FVector RightVector = GetActorRightVector(); // Calculate thrust force (forward + strafe) FVector ThrustForce = (CurrentDirection * CurrentThrottleInput * Acceleration) + (RightVector * CurrentStrafeInput * (Acceleration * 0.7f)); // Apply thrust to velocity CurrentVelocity += ThrustForce * DeltaTime; // Apply drag float DragCoefficient = 0.05f; FVector DragForce = -CurrentVelocity * DragCoefficient; CurrentVelocity += DragForce * DeltaTime; // Limit max speed float CurrentSpeed = CurrentVelocity.Size(); if (CurrentSpeed > MaxSpeed) { CurrentVelocity = CurrentVelocity.GetSafeNormal() * MaxSpeed; } } void ASpaceshipPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked(PlayerInputComponent)) { // Bind throttle input EnhancedInputComponent->BindAction(ThrottleAction, ETriggerEvent::Triggered, this, &ASpaceshipPawn::HandleThrottleInput); // Bind strafe input if available if (StrafeAction) { EnhancedInputComponent->BindAction(StrafeAction, ETriggerEvent::Triggered, this, &ASpaceshipPawn::HandleStrafeInput); } // Bind mouse control EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &ASpaceshipPawn::HandleMouseLook); // Add shooting binding EnhancedInputComponent->BindAction(ShootAction, ETriggerEvent::Triggered, this, &ASpaceshipPawn::HandleShoot); } } void ASpaceshipPawn::HandleThrottleInput(const FInputActionValue& Value) { CurrentThrottleInput = Value.Get(); bThrottlePressed = !FMath::IsNearlyZero(CurrentThrottleInput); } void ASpaceshipPawn::HandleStrafeInput(const FInputActionValue& Value) { CurrentStrafeInput = Value.Get(); } void ASpaceshipPawn::HandleMouseLook(const FInputActionValue& Value) { const FVector2D MouseDelta = Value.Get(); 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; }