feat(character): integrate camera system with aiming mode
Character (C++): - Add camera components to character (SpringArm, Camera, CameraManager) * Initialize camera manager in BeginPlay with component references * SpringArm attached to capsule with 60cm vertical offset * Camera attached to SpringArm socket - Add AimingCameraConfig for over-the-shoulder aiming view * Switch to aiming config when RMB/L2 pressed * Return to default config when aim button released - Improve OnThrowInput code clarity * Simplify controller validation flow * Clean up trajectory calculation comments - Add forward declarations for camera classes - Update class documentation to mention camera integration Blueprint: - Remove legacy camera component (AC_Camera) * Camera rotation now handled by C++ TengriCameraComponent * SpringArm interpolation managed by camera config system - Update look input to use native controller input * AddControllerYawInput/PitchInput for FreeLook mode * Skip input in side-scroller mode (camera is fixed) - Remove camera-related variables (moved to C++ config) - Simplify EventTick (camera logic now in C++ component) - Pass DA_CameraAiming config to character constructor Camera now seamlessly transitions between default and aiming modes, working in tandem with strafe movement for precise targeting.main
parent
74996e5e4b
commit
d89e3fb3b3
|
|
@ -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
|
||||
);
|
||||
if (
|
||||
this.CameraManager.CurrentConfig.BehaviorType ===
|
||||
ETengriCameraBehavior.FreeLook
|
||||
) {
|
||||
this.AddControllerYawInput(actionValueX);
|
||||
this.AddControllerPitchInput(actionValueY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset look input when look action is completed
|
||||
*/
|
||||
EnhancedInputActionLookCompleted(): void {
|
||||
this.CameraComponent.ProcessLookInput(new Vector(0, 0, 0), this.DeltaTime);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)
BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -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';
|
||||
}
|
||||
BIN
Content/Camera/AC_Camera.uasset (Stored with Git LFS)
BIN
Content/Camera/AC_Camera.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UTengriMovementComponent>(TEXT("TengriMovement"));
|
||||
|
||||
// Setup camera system
|
||||
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
|
||||
SpringArmComp->SetupAttachment(CapsuleComponent);
|
||||
SpringArmComp->bUsePawnControlRotation = true;
|
||||
SpringArmComp->SetRelativeLocation(FVector(0.f, 0.f, 60.f));
|
||||
|
||||
CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
|
||||
CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
|
||||
CameraComp->bUsePawnControlRotation = false;
|
||||
|
||||
CameraManager = CreateDefaultSubobject<UTengriCameraComponent>(TEXT("CameraManager"));
|
||||
}
|
||||
|
||||
void ATengriCharacter::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Initialize camera manager with component references
|
||||
if (CameraManager)
|
||||
{
|
||||
CameraManager->InitializeCamera(SpringArmComp, CameraComp);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -188,27 +209,20 @@ void ATengriCharacter::OnThrowInput()
|
|||
// Default to forward direction if raycasting fails
|
||||
FVector ThrowDirection = GetActorForwardVector();
|
||||
|
||||
// ИСПРАВЛЕНИЕ: Переименовали Controller -> PC, чтобы избежать конфликта имен
|
||||
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
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
// Get camera position and rotation
|
||||
// Calculate precise throw trajectory using camera raycast
|
||||
FVector CameraLoc;
|
||||
FRotator CameraRot;
|
||||
PC->GetPlayerViewPoint(CameraLoc, CameraRot); // Используем PC вместо Controller
|
||||
PC->GetPlayerViewPoint(CameraLoc, CameraRot);
|
||||
|
||||
// Raycast from camera forward
|
||||
const FVector TraceStart = CameraLoc;
|
||||
const FVector TraceEnd = CameraLoc + (CameraRot.Vector() * ThrowTraceDistance);
|
||||
|
||||
|
|
@ -217,7 +231,7 @@ void ATengriCharacter::OnThrowInput()
|
|||
QueryParams.AddIgnoredActor(this);
|
||||
QueryParams.AddIgnoredActor(HeldItem);
|
||||
|
||||
// Find world target point (wall/enemy/floor where camera is looking)
|
||||
// Find world target point where camera is looking
|
||||
const bool bHit = World->LineTraceSingleByChannel(
|
||||
Hit,
|
||||
TraceStart,
|
||||
|
|
@ -226,10 +240,9 @@ void ATengriCharacter::OnThrowInput()
|
|||
QueryParams
|
||||
);
|
||||
|
||||
// Use hit point if found, otherwise use far endpoint
|
||||
const FVector TargetPoint = bHit ? Hit.ImpactPoint : TraceEnd;
|
||||
|
||||
// Calculate throw direction FROM item TO target
|
||||
// Calculate throw direction from item to target
|
||||
const FVector HandLocation = HeldItem->GetActorLocation();
|
||||
ThrowDirection = (TargetPoint - HandLocation).GetSafeNormal();
|
||||
}
|
||||
|
|
@ -258,16 +271,13 @@ void ATengriCharacter::OnThrowInput()
|
|||
|
||||
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
|
||||
}
|
||||
|
||||
void ATengriCharacter::OnAimInput(const FInputActionValue& Value)
|
||||
{
|
||||
// Extract boolean state (true = button pressed, false = released)
|
||||
const bool bIsPressed = Value.Get<bool>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
|
|
|||
|
|
@ -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<UTengriMovementComponent> MovementComponent;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
|
||||
TObjectPtr<USpringArmComponent> SpringArmComp;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
|
||||
TObjectPtr<UCameraComponent> CameraComp;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
|
||||
TObjectPtr<UTengriCameraComponent> CameraManager;
|
||||
|
||||
// ========================================================================
|
||||
// CAMERA CONFIGS
|
||||
// ========================================================================
|
||||
|
||||
/** Camera configuration for aiming mode (over-the-shoulder view) */
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Camera|Configs")
|
||||
TObjectPtr<UTengriCameraConfig> 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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue