diff --git a/Content/Blueprints/BP_MainCharacter.ts b/Content/Blueprints/BP_MainCharacter.ts index d0e283b..55f94ab 100644 --- a/Content/Blueprints/BP_MainCharacter.ts +++ b/Content/Blueprints/BP_MainCharacter.ts @@ -1,6 +1,5 @@ // Content/Blueprints/BP_MainCharacter.ts -import { AC_Camera } from '/Content/Camera/AC_Camera.ts'; import { AC_DebugHUD } from '/Content/Debug/Components/AC_DebugHUD.ts'; import { AC_InputDevice } from '/Content/Input/Components/AC_InputDevice.ts'; import { IMC_Default } from '/Content/Input/IMC_Default.ts'; @@ -11,11 +10,8 @@ import { EnhancedInputLocalPlayerSubsystem } from '/Content/UE/EnhancedInputLoca import type { Float } from '/Content/UE/Float.ts'; import { MathLibrary } from '/Content/UE/MathLibrary.ts'; import type { PlayerController } from '/Content/UE/PlayerController.ts'; -import { Rotator } from '/Content/UE/Rotator.ts'; import { SystemLibrary } from '/Content/UE/SystemLibrary.ts'; import { Vector } from '/Content/UE/Vector.ts'; -import { TengriMovementComponent } from '/Source/TengriPlatformer/Movement/TengriMovementComponent.ts'; -import { DA_TengriMovementConfig } from '/Content/Movement/DA_TengriMovementConfig.ts'; import { TengriCharacter } from '/Source/TengriPlatformer/Character/TengriCharacter.ts'; import { IA_Interact } from '/Content/Input/Actions/IA_Inreract.ts'; import { IA_Throw } from '/Content/Input/Actions/IA_Throw.ts'; @@ -23,9 +19,9 @@ import { IA_Aim } from '/Content/Input/Actions/IA_Aim.ts'; import { IMC_ItemHeld } from '/Content/Input/IMC_ItemHeld.ts'; import { CreateWidget } from '/Content/UE/CteateWidget.ts'; import { WBP_HUD } from '/Content/UI/WBP_HUD.ts'; -import { SpringArmComponent } from '/Content/UE/SpringArmComponent.ts'; -import { LinearColor } from '/Content/UE/LinearColor.ts'; import { CustomDefaultSkeletalMesh } from '/Content/BasicShapes/CustomDefaultSkeletalMesh.ts'; +import { ETengriCameraBehavior } from '/Source/TengriPlatformer/Camera/Core/TengriCameraConfig.ts'; +import { DA_CameraAiming } from '/Content/Camera/DA_CameraAiming.ts'; /** * Main Character Blueprint @@ -90,17 +86,13 @@ export class BP_MainCharacter extends TengriCharacter { actionValueX: Float, actionValueY: Float ): void { - this.CameraComponent.ProcessLookInput( - new Vector(actionValueX, actionValueY, 0), - this.DeltaTime - ); - } - - /** - * Reset look input when look action is completed - */ - EnhancedInputActionLookCompleted(): void { - this.CameraComponent.ProcessLookInput(new Vector(0, 0, 0), this.DeltaTime); + if ( + this.CameraManager.CurrentConfig.BehaviorType === + ETengriCameraBehavior.FreeLook + ) { + this.AddControllerYawInput(actionValueX); + this.AddControllerPitchInput(actionValueY); + } } /** @@ -132,7 +124,7 @@ export class BP_MainCharacter extends TengriCharacter { return new Vector(vec1.X + vec2.X, vec1.Y + vec2.Y, 0); }; - this.TengriMovement.SetInputVector( + this.MovementComponent.SetInputVector( CalculateResultMovementInputVector( MathLibrary.GetRightVector( this.GetControlRotation().roll, @@ -150,7 +142,7 @@ export class BP_MainCharacter extends TengriCharacter { * Reset movement input when move action is completed */ EnhancedInputActionMoveCompleted(): void { - this.TengriMovement.SetInputVector(new Vector(0, 0, 0)); + this.MovementComponent.SetInputVector(new Vector(0, 0, 0)); } EnhancedInputActionJumpTriggered(): void { @@ -161,24 +153,6 @@ export class BP_MainCharacter extends TengriCharacter { this.MovementComponent.SetJumpInput(false); } - MovementComponentOnLanded(IsHeavy: boolean): void { - if (IsHeavy) { - SystemLibrary.PrintString( - 'Boom! (Heavy)', - true, - true, - new LinearColor(1, 0, 0, 1) - ); - } else { - SystemLibrary.PrintString( - 'Tap (Light)', - true, - true, - new LinearColor(0, 1, 0, 1) - ); - } - } - /** * Initialize all systems when character spawns * Order: Toast → Debug → Movement (movement last as it may generate debug output) @@ -200,11 +174,6 @@ export class BP_MainCharacter extends TengriCharacter { ); } - this.CameraComponent.InitializeCameraSystem( - this.InputDeviceComponent, - this.DebugHUDComponent - ); - CreateWidget(WBP_HUD).AddToViewport(); } @@ -212,60 +181,21 @@ export class BP_MainCharacter extends TengriCharacter { * Update all systems each frame * Called by Unreal Engine game loop */ - EventTick(DeltaTime: Float): void { - this.DeltaTime = DeltaTime; - + EventTick(): void { if (this.ShowDebugInfo) { this.DebugHUDComponent.UpdateHUD(SystemLibrary.GetGameTimeInSeconds()); this.ToastSystemComponent.UpdateToastSystem(); } - this.CameraComponent.UpdateCameraRotation(DeltaTime); - - this.GetController().SetControlRotation( - new Rotator( - 0, - this.CameraComponent.GetCameraRotation().Pitch, - this.CameraComponent.GetCameraRotation().Yaw - ) - ); - if (this.ShowDebugInfo) { this.InputDeviceComponent.UpdateDebugPage(); - this.CameraComponent.UpdateDebugPage(); } - - this.SpringArm.TargetArmLength = MathLibrary.FInterpTo( - this.SpringArm.TargetArmLength, - this.bIsAiming ? this.AimArmLength : this.DefaultArmLength, - DeltaTime, - 10.0 - ); - - this.SpringArm.TargetOffset = MathLibrary.VInterpTo( - this.SpringArm.TargetOffset, - this.bIsAiming ? this.AimSocketOffset : this.DefaultSocketOffset, - DeltaTime, - 10.0 - ); } // ════════════════════════════════════════════════════════════════════════════════════════ // VARIABLES // ════════════════════════════════════════════════════════════════════════════════════════ - /** - * Camera system component - handles camera rotation and sensitivity - * @category Components - */ - SpringArm = new SpringArmComponent(); - - /** - * Camera system component - handles camera rotation and sensitivity - * @category Components - */ - CameraComponent = new AC_Camera(); - /** * Input device detection component - manages input device state and detection * @category Components @@ -278,10 +208,6 @@ export class BP_MainCharacter extends TengriCharacter { */ ToastSystemComponent = new AC_ToastSystem(); - TengriMovement = new TengriMovementComponent({ - MovementConfig: DA_TengriMovementConfig, - }); - /** * Debug HUD system - displays movement parameters and performance metrics * @category Components @@ -295,37 +221,8 @@ export class BP_MainCharacter extends TengriCharacter { */ private ShowDebugInfo: boolean = true; - /** - * @category Camera - * @instanceEditable true - */ - private readonly DefaultArmLength: Float = 400.0; - - /** - * @category Camera - * @instanceEditable true - */ - private readonly AimArmLength: Float = 250.0; - - /** - * @category Camera - * @instanceEditable true - */ - private readonly DefaultSocketOffset: Vector = new Vector(0.0, 0.0, 0.0); - - /** - * @category Camera - * @instanceEditable true - */ - private readonly AimSocketOffset: Vector = new Vector(0.0, 100.0, 60.0); - - /** - * Cached delta time from last tick - used for time-based calculations - */ - private DeltaTime: Float = 0.0; - constructor() { - super(); + super(new DA_CameraAiming()); this.InteractAction = IA_Interact; this.ThrowAction = IA_Throw; diff --git a/Content/Blueprints/BP_MainCharacter.uasset b/Content/Blueprints/BP_MainCharacter.uasset index 78e498c..31b231f 100644 --- a/Content/Blueprints/BP_MainCharacter.uasset +++ b/Content/Blueprints/BP_MainCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc6a7a86960a45d00c942deee974df62de7897a9432ec37d4db8e34996cf59a1 -size 413601 +oid sha256:4b1ef00f3c11f790cf69fbc6c07a2e24668ec1ac66cd926a054cf0f745353b1e +size 302029 diff --git a/Content/Camera/AC_Camera.ts b/Content/Camera/AC_Camera.ts deleted file mode 100644 index 206773f..0000000 --- a/Content/Camera/AC_Camera.ts +++ /dev/null @@ -1,323 +0,0 @@ -// Content/Camera/Components/AC_Camera.ts - -import type { AC_DebugHUD } from '/Content/Debug/Components/AC_DebugHUD.ts'; -import type { AC_InputDevice } from '/Content/Input/Components/AC_InputDevice.ts'; -import { ActorComponent } from '/Content/UE/ActorComponent.ts'; -import type { Float } from '/Content/UE/Float.ts'; -import { MathLibrary } from '/Content/UE/MathLibrary.ts'; -import { SystemLibrary } from '/Content/UE/SystemLibrary.ts'; -import { Vector } from '/Content/UE/Vector.ts'; - -/** - * Camera System Component - * Deterministic camera control with smooth rotation and device-aware sensitivity - * Provides precise control over camera behavior for consistent experience - */ -export class AC_Camera extends ActorComponent { - // ════════════════════════════════════════════════════════════════════════════════════════ - // FUNCTIONS - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Process look input and update camera rotation - * @param InputDelta - Input delta (X = Yaw, Y = Pitch) - * @param DeltaTime - Time since last frame - * @category Input Processing - */ - public ProcessLookInput(InputDelta: Vector, DeltaTime: Float): void { - if (this.IsInitialized) { - const invertMultiplier = this.InvertYAxis ? -1.0 : 1.0; - let sensitivity: Float = 0; - - if (SystemLibrary.IsValid(this.InputDeviceComponent)) { - sensitivity = this.InputDeviceComponent.IsGamepad() - ? this.GamepadSensitivity - : this.MouseSensitivity; - } else { - sensitivity = this.MouseSensitivity; - } - - const CalculateTargetPitch = ( - targetPitch: Float, - inputDeltaY: Float, - deltaTime: Float - ): Float => - targetPitch - inputDeltaY * sensitivity * invertMultiplier * deltaTime; - - const CalculateTargetYaw = ( - targetYaw: Float, - inputDeltaX: Float, - deltaTime: Float - ): Float => targetYaw + inputDeltaX * sensitivity * deltaTime; - - this.TargetPitch = MathLibrary.ClampFloat( - CalculateTargetPitch(this.TargetPitch, InputDelta.Y, DeltaTime), - this.PitchMin, - this.PitchMax - ); - - this.TargetYaw = CalculateTargetYaw( - this.TargetYaw, - InputDelta.X, - DeltaTime - ); - - this.InputMagnitude = MathLibrary.VectorLength(InputDelta); - } - } - - /** - * Update camera rotation with smooth interpolation - * @param DeltaTime - Time since last frame - * @category Camera Updates - */ - public UpdateCameraRotation(DeltaTime: Float): void { - if (this.IsInitialized) { - if (this.SmoothingSpeed > 0) { - // Smooth interpolation to target rotation - this.CurrentPitch = MathLibrary.FInterpTo( - this.CurrentPitch, - this.TargetPitch, - DeltaTime, - this.SmoothingSpeed - ); - - this.CurrentYaw = MathLibrary.FInterpTo( - this.CurrentYaw, - this.TargetYaw, - DeltaTime, - this.SmoothingSpeed - ); - } else { - // Instant rotation (no smoothing) - this.CurrentPitch = this.TargetPitch; - this.CurrentYaw = this.TargetYaw; - } - } - } - - /** - * Get current camera rotation for applying to SpringArm - * @returns Current camera rotation values - * @category Public Interface - * @pure true - */ - public GetCameraRotation(): { Pitch: Float; Yaw: Float } { - return { - Pitch: this.CurrentPitch, - Yaw: this.CurrentYaw, - }; - } - - /** - * Check if camera is currently rotating - * @returns True if there's active rotation input - * @category State Queries - * @pure true - */ - public IsCameraRotating(): boolean { - return this.InputMagnitude > 0.01; - } - - /** - * Initialize camera system with default settings - * @category System Setup - */ - public InitializeCameraSystem( - InputDeviceRef: AC_InputDevice, - DebugComponentRef: AC_DebugHUD - ): void { - this.InputDeviceComponent = InputDeviceRef; - this.DebugHUDComponent = DebugComponentRef; - - this.IsInitialized = true; - - if (SystemLibrary.IsValid(this.DebugHUDComponent)) { - this.DebugHUDComponent.AddDebugPage( - this.DebugPageID, - 'Camera System', - 60 - ); - } - } - - /** - * Update debug HUD with current camera info - * @category Debug - */ - public UpdateDebugPage(): void { - if (SystemLibrary.IsValid(this.DebugHUDComponent)) { - if ( - this.DebugHUDComponent.ShouldUpdatePage( - this.DebugPageID, - SystemLibrary.GetGameTimeInSeconds() - ) - ) { - this.DebugHUDComponent.UpdatePageContent( - this.DebugPageID, - `Current Device: ${SystemLibrary.IsValid(this.InputDeviceComponent) ? this.InputDeviceComponent.GetCurrentInputDevice() : 'Input Device Component Not Found'}\n` + - `Sensitivity: ${SystemLibrary.IsValid(this.InputDeviceComponent) && this.InputDeviceComponent.IsGamepad() ? this.GamepadSensitivity : this.MouseSensitivity}\n` + - `Pitch: ${this.GetCameraRotation().Pitch}°\n` + - `Yaw: ${this.GetCameraRotation().Yaw}°\n` + - `Is Rotating: ${this.IsCameraRotating() ? 'Yes' : 'No'}\n` + - `Smoothing: ${this.SmoothingSpeed}\n` + - `Invert Y: ${this.InvertYAxis ? 'Yes' : 'No'}` - ); - } - } - } - - /** - * Get camera configuration and state data for testing purposes - * Provides read-only access to private variables for automated tests - * Only includes essential data needed for test validation - * @category Debug - * @returns Object containing camera settings (sensitivity, pitch limits) for test assertions - */ - public GetTestData(): { - MouseSensitivity: Float; - GamepadSensitivity: Float; - PitchMin: Float; - PitchMax: Float; - } { - return { - MouseSensitivity: this.MouseSensitivity, - GamepadSensitivity: this.GamepadSensitivity, - PitchMin: this.PitchMin, - PitchMax: this.PitchMax, - }; - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Mouse sensitivity multiplier for camera rotation - * Higher values result in faster camera movement with mouse input - * Typical range: 50.0 (slow) - 200.0 (fast) - * @category Camera Config - * @instanceEditable true - * @default 100.0 - */ - private readonly MouseSensitivity: Float = 100.0; - - /** - * Gamepad sensitivity multiplier for camera rotation - * Higher than mouse sensitivity to compensate for analog stick precision - * Typical range: 100.0 (slow) - 300.0 (fast) - * @category Camera Config - * @instanceEditable true - * @default 150.0 - */ - private readonly GamepadSensitivity: Float = 150.0; - - /** - * Invert vertical axis for camera rotation - * When true, pushing up on input rotates camera down and vice versa - * Common preference for flight-sim style controls - * @category Camera Config - * @instanceEditable true - * @default false - */ - private readonly InvertYAxis: boolean = false; - - /** - * Minimum pitch angle in degrees (looking down) - * Prevents camera from rotating beyond this angle - * Set to -89° to avoid gimbal lock at -90° - * @category Camera Config - * @instanceEditable true - * @default -89.0 - */ - private readonly PitchMin: Float = -89.0; - - /** - * Maximum pitch angle in degrees (looking up) - * Prevents camera from rotating beyond this angle - * Set to +89° to avoid gimbal lock at +90° - * @category Camera Config - * @instanceEditable true - * @default 89.0 - */ - private readonly PitchMax: Float = 89.0; - - /** - * Speed of smooth interpolation to target rotation - * Higher values make camera more responsive but less smooth - * Set to 0 for instant rotation without interpolation - * Typical range: 10.0 (smooth) - 30.0 (responsive) - * @category Camera Config - * @instanceEditable true - * @default 20.0 - */ - private readonly SmoothingSpeed: Float = 20.0; - - /** - * Current pitch angle for rendering - * Smoothly interpolates towards TargetPitch based on SmoothingSpeed - * Updated every frame by UpdateCameraRotation() - * @category Camera State - */ - private CurrentPitch: Float = 0; - - /** - * Current yaw angle for rendering - * Smoothly interpolates towards TargetYaw based on SmoothingSpeed - * Updated every frame by UpdateCameraRotation() - * @category Camera State - */ - private CurrentYaw: Float = 0; - - /** - * Target pitch angle from player input - * Updated by ProcessLookInput() based on input delta - * Clamped to PitchMin/PitchMax range - * @category Camera State - */ - private TargetPitch: Float = 0; - - /** - * Target yaw angle from player input - * Updated by ProcessLookInput() based on input delta - * No clamping - can rotate freely beyond 360° - * @category Camera State - */ - private TargetYaw: Float = 0; - - /** - * Magnitude of current input vector - * Used by IsCameraRotating() to detect active camera input - * Cached to avoid recalculating VectorLength every frame - * @category Camera State - * @default 0.0 - */ - private InputMagnitude: Float = 0; - - /** - * System initialization state flag - * @category Camera State - */ - private IsInitialized: boolean = false; - - /** - * Reference to input device component for device detection - * Set externally, used for sensitivity adjustments - * @category Components - */ - public InputDeviceComponent: AC_InputDevice | null = null; - - /** - * Reference to debug HUD component for displaying camera info - * Optional, used for debugging purposes - * @category Components - */ - public DebugHUDComponent: AC_DebugHUD | null = null; - - /** - * Debug page identifier for organizing debug output - * Used by debug HUD to categorize information - * @category Debug - */ - public readonly DebugPageID: string = 'CameraInfo'; -} diff --git a/Content/Camera/AC_Camera.uasset b/Content/Camera/AC_Camera.uasset deleted file mode 100644 index 4d3f9a1..0000000 --- a/Content/Camera/AC_Camera.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c82f7e8be8be6b6b7e5b5340cef44769f2d89fb99b4290a527252bdd32660d54 -size 328871 diff --git a/Content/UE/CameraComponent.ts b/Content/UE/CameraComponent.ts new file mode 100644 index 0000000..3bd65e5 --- /dev/null +++ b/Content/UE/CameraComponent.ts @@ -0,0 +1,10 @@ +// Content/UE/CameraComponent.ts + +import { SceneComponent } from '/Content/UE/SceneComponent.ts'; +import type { UEObject } from '/Content/UE/UEObject.ts'; + +export class CameraComponent extends SceneComponent { + constructor(outer: UEObject | null = null, name: string = 'None') { + super(outer, name); + } +} diff --git a/Content/UE/Pawn.ts b/Content/UE/Pawn.ts index dd4a8b9..8bd3a55 100644 --- a/Content/UE/Pawn.ts +++ b/Content/UE/Pawn.ts @@ -5,6 +5,7 @@ import { Controller } from '/Content/UE/Controller.ts'; import { Name } from '/Content/UE/Name.ts'; import { Rotator } from '/Content/UE/Rotator.ts'; import { UEObject } from '/Content/UE/UEObject.ts'; +import type { Float } from '/Content/UE/Float.ts'; export class Pawn extends Actor { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { @@ -18,4 +19,12 @@ export class Pawn extends Actor { public GetControlRotation(): Rotator { return new Rotator(); } + + public AddControllerYawInput(Val: Float): void { + console.log(Val); + } + + public AddControllerPitchInput(Val: Float): void { + console.log(Val); + } } diff --git a/Source/TengriPlatformer/Character/TengriCharacter.cpp b/Source/TengriPlatformer/Character/TengriCharacter.cpp index b27f15b..11031e4 100644 --- a/Source/TengriPlatformer/Character/TengriCharacter.cpp +++ b/Source/TengriPlatformer/Character/TengriCharacter.cpp @@ -6,7 +6,10 @@ #include "Components/CapsuleComponent.h" #include "Components/SkeletalMeshComponent.h" #include "Components/ArrowComponent.h" +#include "GameFramework/SpringArmComponent.h" +#include "Camera/CameraComponent.h" #include "TengriPlatformer/Movement/TengriMovementComponent.h" +#include "TengriPlatformer/Camera/TengriCameraComponent.h" #include "TengriPlatformer/World/TengriPickupActor.h" #include "Kismet/KismetSystemLibrary.h" @@ -54,11 +57,29 @@ ATengriCharacter::ATengriCharacter() // Setup custom movement component MovementComponent = CreateDefaultSubobject(TEXT("TengriMovement")); + + // Setup camera system + SpringArmComp = CreateDefaultSubobject(TEXT("SpringArm")); + SpringArmComp->SetupAttachment(CapsuleComponent); + SpringArmComp->bUsePawnControlRotation = true; + SpringArmComp->SetRelativeLocation(FVector(0.f, 0.f, 60.f)); + + CameraComp = CreateDefaultSubobject(TEXT("Camera")); + CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName); + CameraComp->bUsePawnControlRotation = false; + + CameraManager = CreateDefaultSubobject(TEXT("CameraManager")); } void ATengriCharacter::BeginPlay() { Super::BeginPlay(); + + // Initialize camera manager with component references + if (CameraManager) + { + CameraManager->InitializeCamera(SpringArmComp, CameraComp); + } } // ============================================================================ @@ -178,96 +199,85 @@ void ATengriCharacter::Interact() void ATengriCharacter::OnThrowInput() { - if (!HeldItem) - { - UE_LOG(LogTengriCharacter, Warning, - TEXT("OnThrowInput: No item held")); - return; - } + if (!HeldItem) + { + UE_LOG(LogTengriCharacter, Warning, + TEXT("OnThrowInput: No item held")); + return; + } - // Default to forward direction if raycasting fails - FVector ThrowDirection = GetActorForwardVector(); + // Default to forward direction if raycasting fails + FVector ThrowDirection = GetActorForwardVector(); - // ИСПРАВЛЕНИЕ: Переименовали Controller -> PC, чтобы избежать конфликта имен - AController* PC = GetController(); + AController* PC = GetController(); - if (UWorld* World = GetWorld(); !PC || !World) - { - UE_LOG(LogTengriCharacter, Warning, - TEXT("OnThrowInput: Invalid controller or world context")); - // Continue with fallback direction - } - else - { - // ───────────────────────────────────────────────────────────────── - // PRECISE THROW TRAJECTORY CALCULATION - // ───────────────────────────────────────────────────────────────── + if (UWorld* World = GetWorld(); !PC || !World) + { + UE_LOG(LogTengriCharacter, Warning, + TEXT("OnThrowInput: Invalid controller or world context")); + } + else + { + // Calculate precise throw trajectory using camera raycast + FVector CameraLoc; + FRotator CameraRot; + PC->GetPlayerViewPoint(CameraLoc, CameraRot); - // Get camera position and rotation - FVector CameraLoc; - FRotator CameraRot; - PC->GetPlayerViewPoint(CameraLoc, CameraRot); // Используем PC вместо Controller + const FVector TraceStart = CameraLoc; + const FVector TraceEnd = CameraLoc + (CameraRot.Vector() * ThrowTraceDistance); - // Raycast from camera forward - const FVector TraceStart = CameraLoc; - const FVector TraceEnd = CameraLoc + (CameraRot.Vector() * ThrowTraceDistance); + FHitResult Hit; + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(this); + QueryParams.AddIgnoredActor(HeldItem); - FHitResult Hit; - FCollisionQueryParams QueryParams; - QueryParams.AddIgnoredActor(this); - QueryParams.AddIgnoredActor(HeldItem); + // Find world target point where camera is looking + const bool bHit = World->LineTraceSingleByChannel( + Hit, + TraceStart, + TraceEnd, + ECC_Visibility, + QueryParams + ); - // Find world target point (wall/enemy/floor where camera is looking) - const bool bHit = World->LineTraceSingleByChannel( - Hit, - TraceStart, - TraceEnd, - ECC_Visibility, - QueryParams - ); + const FVector TargetPoint = bHit ? Hit.ImpactPoint : TraceEnd; - // Use hit point if found, otherwise use far endpoint - const FVector TargetPoint = bHit ? Hit.ImpactPoint : TraceEnd; + // Calculate throw direction from item to target + const FVector HandLocation = HeldItem->GetActorLocation(); + ThrowDirection = (TargetPoint - HandLocation).GetSafeNormal(); + } - // Calculate throw direction FROM item TO target - const FVector HandLocation = HeldItem->GetActorLocation(); - ThrowDirection = (TargetPoint - HandLocation).GetSafeNormal(); - } + // Add arc elevation for natural parabolic trajectory + ThrowDirection += FVector(0.0f, 0.0f, ThrowArcElevation); + ThrowDirection.Normalize(); - // Add arc elevation for natural parabolic trajectory - ThrowDirection += FVector(0.0f, 0.0f, ThrowArcElevation); - ThrowDirection.Normalize(); + // Rotate character instantly to face throw direction + FRotator CharacterFaceRot = ThrowDirection.Rotation(); + CharacterFaceRot.Pitch = 0.0f; + CharacterFaceRot.Roll = 0.0f; + + if (MovementComponent) + { + MovementComponent->ForceRotation(CharacterFaceRot); + } - // Rotate character instantly to face throw direction - FRotator CharacterFaceRot = ThrowDirection.Rotation(); - CharacterFaceRot.Pitch = 0.0f; - CharacterFaceRot.Roll = 0.0f; - - if (MovementComponent) - { - MovementComponent->ForceRotation(CharacterFaceRot); - } + // Execute throw with calculated trajectory + const FVector Impulse = ThrowDirection * ThrowForce; + HeldItem->OnDropped(Impulse, true); + HeldItem = nullptr; - // Execute throw with calculated trajectory - const FVector Impulse = ThrowDirection * ThrowForce; - HeldItem->OnDropped(Impulse, true); - HeldItem = nullptr; - - // Disable combat controls - ToggleItemHeldContext(false); - - UE_LOG(LogTengriCharacter, Verbose, - TEXT("Threw item with force: %.1f cm/s"), ThrowForce); - - // Note: bIsAiming state will be cleared by OnAimInput when player releases RMB/L2 + // Disable combat controls + ToggleItemHeldContext(false); + + UE_LOG(LogTengriCharacter, Verbose, + TEXT("Threw item with force: %.1f cm/s"), ThrowForce); } void ATengriCharacter::OnAimInput(const FInputActionValue& Value) { - // Extract boolean state (true = button pressed, false = released) const bool bIsPressed = Value.Get(); - // Skip if state hasn't changed (optimization) + // Skip if state hasn't changed if (bIsPressed == bIsAiming) { return; @@ -279,9 +289,24 @@ void ATengriCharacter::OnAimInput(const FInputActionValue& Value) if (MovementComponent) { MovementComponent->SetStrafing(bIsAiming); - UE_LOG(LogTengriCharacter, Verbose, - TEXT("Aim mode: %s"), bIsAiming ? TEXT("ON") : TEXT("OFF")); } + + // Switch camera configuration for aiming + if (CameraManager) + { + if (bIsAiming && AimingCameraConfig) + { + CameraManager->SetCameraConfig(AimingCameraConfig); + } + else + { + // Return to default config (nullptr triggers fallback) + CameraManager->SetCameraConfig(nullptr); + } + } + + UE_LOG(LogTengriCharacter, Verbose, + TEXT("Aim mode: %s"), bIsAiming ? TEXT("ON") : TEXT("OFF")); } // ============================================================================ @@ -290,7 +315,7 @@ void ATengriCharacter::OnAimInput(const FInputActionValue& Value) ATengriPickupActor* ATengriCharacter::FindNearestPickup() const { - UWorld* World = GetWorld(); + const UWorld* World = GetWorld(); if (!World) { return nullptr; @@ -331,8 +356,8 @@ ATengriPickupActor* ATengriCharacter::FindNearestPickup() const continue; } - const float DistSq = FVector::DistSquared(SpherePos, Item->GetActorLocation()); - if (DistSq < MinDistSq) + if (const float DistSq = FVector::DistSquared(SpherePos, Item->GetActorLocation()); + DistSq < MinDistSq) { MinDistSq = DistSq; NearestItem = Item; diff --git a/Source/TengriPlatformer/Character/TengriCharacter.h b/Source/TengriPlatformer/Character/TengriCharacter.h index 9fe028d..9953ad8 100644 --- a/Source/TengriPlatformer/Character/TengriCharacter.h +++ b/Source/TengriPlatformer/Character/TengriCharacter.h @@ -12,15 +12,20 @@ // Forward declarations class UCapsuleComponent; class USkeletalMeshComponent; -class UTengriMovementComponent; -class ATengriPickupActor; class UArrowComponent; +class USpringArmComponent; +class UCameraComponent; +class UTengriMovementComponent; +class UTengriCameraComponent; +class UTengriCameraConfig; +class ATengriPickupActor; class UInputMappingContext; class UInputAction; /** * Main player character class with item interaction and throwing mechanics. * Supports dynamic input context switching for item-based actions. + * Features integrated camera system with multiple behavior modes. */ UCLASS() class TENGRIPLATFORMER_API ATengriCharacter : public APawn @@ -51,6 +56,23 @@ public: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") TObjectPtr MovementComponent; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + TObjectPtr SpringArmComp; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + TObjectPtr CameraComp; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + TObjectPtr CameraManager; + + // ======================================================================== + // CAMERA CONFIGS + // ======================================================================== + + /** Camera configuration for aiming mode (over-the-shoulder view) */ + UPROPERTY(EditDefaultsOnly, Category = "Camera|Configs") + TObjectPtr AimingCameraConfig; + // ======================================================================== // INPUT CONFIG // ======================================================================== @@ -94,7 +116,7 @@ public: /** * Handle aim input state changes (RMB/L2). - * Toggles strafe mode on MovementComponent for camera-aligned rotation. + * Toggles strafe mode and switches camera configuration. * @param Value - Input action value (true = pressed, false = released) */ void OnAimInput(const FInputActionValue& Value); diff --git a/Source/TengriPlatformer/Character/TengriCharacter.ts b/Source/TengriPlatformer/Character/TengriCharacter.ts index fd60ab0..8ae9d87 100644 --- a/Source/TengriPlatformer/Character/TengriCharacter.ts +++ b/Source/TengriPlatformer/Character/TengriCharacter.ts @@ -7,15 +7,24 @@ import { ArrowComponent } from '/Content/UE/ArrowComponent.ts'; import { TengriMovementComponent } from '/Source/TengriPlatformer/Movement/TengriMovementComponent.ts'; import { InputAction } from '/Content/UE/InputAction.ts'; import { InputMappingContext } from '/Content/UE/InputMappingContext.ts'; +import { SpringArmComponent } from '/Content/UE/SpringArmComponent.ts'; +import { CameraComponent } from '/Content/UE/CameraComponent.ts'; +import { TengriCameraComponent } from '/Source/TengriPlatformer/Camera/TengriCameraComponent.ts'; +import { TengriCameraConfig } from '/Source/TengriPlatformer/Camera/Core/TengriCameraConfig.ts'; export class TengriCharacter extends Pawn { - constructor() { + constructor(AimingCameraConfig: TengriCameraConfig) { super(); this.CapsuleComponent = new CapsuleComponent(); this.Mesh = new SkeletalMesh(); this.ArrowComponent = new ArrowComponent(); this.MovementComponent = new TengriMovementComponent(); + this.SpringArmComponent = new SpringArmComponent(); + this.CameraComponent = new CameraComponent(); + this.CameraManager = new TengriCameraComponent(); + + this.AimingCameraConfig = AimingCameraConfig; this.InteractAction = new InputAction(); this.ThrowAction = new InputAction(); @@ -31,6 +40,15 @@ export class TengriCharacter extends Pawn { public Mesh: SkeletalMesh; public ArrowComponent: ArrowComponent; public MovementComponent: TengriMovementComponent; + public SpringArmComponent: SpringArmComponent; + public CameraComponent: CameraComponent; + public CameraManager: TengriCameraComponent; + + // ======================================================================== + // CAMERA CONFIGS + // ======================================================================== + + public AimingCameraConfig: TengriCameraConfig; // ======================================================================== // INPUT CONFIG