#include "SpaceshipPawn.h" #include "GameFramework/SpringArmComponent.h" #include "Camera/CameraComponent.h" #include "Camera/CameraShakeBase.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); ShipMesh->SetGenerateOverlapEvents(true); } } 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(); } } } // Initialize health and shield values CurrentHealth = MaxHealth; CurrentShield = MaxShield; LastDamageTime = 0.0f; // Initialize the dynamic material InitializeDynamicMaterial(); } 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.95f)); // 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; } 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::InitializeDynamicMaterial() { if (ShipMesh) { // Store the original material OriginalMaterial = ShipMesh->GetMaterial(0); if (OriginalMaterial) { // Create dynamic material instance only once DynamicMaterialInstance = UMaterialInstanceDynamic::Create(OriginalMaterial, this); if (DynamicMaterialInstance) { // Apply the dynamic material instance to the mesh ShipMesh->SetMaterial(0, DynamicMaterialInstance); // Initialize flash color to transparent DynamicMaterialInstance->SetVectorParameterValue("FlashColor", FLinearColor(0.0f, 0.0f, 0.0f, 0.0f)); } } } } void ASpaceshipPawn::ApplyDamageFlash() { // Only set the flash color if we have a valid dynamic material instance if (DynamicMaterialInstance) { // Set the flash color DynamicMaterialInstance->SetVectorParameterValue("FlashColor", DamageFlashColor); // Set timer to reset the flash GetWorldTimerManager().SetTimer( DamageFlashTimerHandle, this, &ASpaceshipPawn::ResetDamageFlash, DamageFlashDuration, false ); } } void ASpaceshipPawn::ResetDamageFlash() { // Reset the flash color only if we have a valid dynamic material instance if (DynamicMaterialInstance) { DynamicMaterialInstance->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(GetController()); if (PC) { DisableInput(PC); } // For now, just log the death if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("PLAYER DIED!")); } }