Compare commits
No commits in common. "3688dc9acd0f60d38eb198d172e5182fd9392266" and "11596690cd1d013750caba2f264695a886202d28" have entirely different histories.
3688dc9acd
...
11596690cd
|
|
@ -38,6 +38,3 @@ coverage/
|
|||
# Package manager files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Markdown
|
||||
*.md
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// Camera/Components/AC_Camera.ts
|
||||
|
||||
import type { S_CameraSettings } from '#root/Camera/Structs/S_CameraSettings.ts';
|
||||
import type { S_CameraState } from '#root/Camera/Structs/S_CameraState.ts';
|
||||
import type { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
||||
import type { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts';
|
||||
import { ActorComponent } from '#root/UE/ActorComponent.ts';
|
||||
|
|
@ -26,15 +28,15 @@ export class AC_Camera extends ActorComponent {
|
|||
*/
|
||||
public ProcessLookInput(InputDelta: Vector, DeltaTime: Float): void {
|
||||
if (this.IsInitialized) {
|
||||
const invertMultiplier = this.InvertYAxis ? -1.0 : 1.0;
|
||||
const invertMultiplier = this.CameraSettings.InvertYAxis ? -1.0 : 1.0;
|
||||
let sensitivity: Float = 0;
|
||||
|
||||
if (SystemLibrary.IsValid(this.InputDeviceComponent)) {
|
||||
sensitivity = this.InputDeviceComponent.IsGamepad()
|
||||
? this.GamepadSensitivity
|
||||
: this.MouseSensitivity;
|
||||
? this.CameraSettings.GamepadSensitivity
|
||||
: this.CameraSettings.MouseSensitivity;
|
||||
} else {
|
||||
sensitivity = this.MouseSensitivity;
|
||||
sensitivity = this.CameraSettings.MouseSensitivity;
|
||||
}
|
||||
|
||||
const CalculateTargetPitch = (
|
||||
|
|
@ -50,19 +52,26 @@ export class AC_Camera extends ActorComponent {
|
|||
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);
|
||||
this.CameraState = {
|
||||
CurrentPitch: this.CameraState.CurrentPitch,
|
||||
CurrentYaw: this.CameraState.CurrentYaw,
|
||||
TargetPitch: MathLibrary.ClampFloat(
|
||||
CalculateTargetPitch(
|
||||
this.CameraState.TargetPitch,
|
||||
InputDelta.Y,
|
||||
DeltaTime
|
||||
),
|
||||
this.CameraSettings.PitchMin,
|
||||
this.CameraSettings.PitchMax
|
||||
),
|
||||
TargetYaw: CalculateTargetYaw(
|
||||
this.CameraState.TargetYaw,
|
||||
InputDelta.X,
|
||||
DeltaTime
|
||||
),
|
||||
LastInputDelta: InputDelta,
|
||||
InputMagnitude: MathLibrary.VectorLength(InputDelta),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,25 +82,25 @@ export class AC_Camera extends ActorComponent {
|
|||
*/
|
||||
public UpdateCameraRotation(DeltaTime: Float): void {
|
||||
if (this.IsInitialized) {
|
||||
if (this.SmoothingSpeed > 0) {
|
||||
if (this.CameraSettings.SmoothingSpeed > 0) {
|
||||
// Smooth interpolation to target rotation
|
||||
this.CurrentPitch = MathLibrary.FInterpTo(
|
||||
this.CurrentPitch,
|
||||
this.TargetPitch,
|
||||
this.CameraState.CurrentPitch = MathLibrary.FInterpTo(
|
||||
this.CameraState.CurrentPitch,
|
||||
this.CameraState.TargetPitch,
|
||||
DeltaTime,
|
||||
this.SmoothingSpeed
|
||||
this.CameraSettings.SmoothingSpeed
|
||||
);
|
||||
|
||||
this.CurrentYaw = MathLibrary.FInterpTo(
|
||||
this.CurrentYaw,
|
||||
this.TargetYaw,
|
||||
this.CameraState.CurrentYaw = MathLibrary.FInterpTo(
|
||||
this.CameraState.CurrentYaw,
|
||||
this.CameraState.TargetYaw,
|
||||
DeltaTime,
|
||||
this.SmoothingSpeed
|
||||
this.CameraSettings.SmoothingSpeed
|
||||
);
|
||||
} else {
|
||||
// Instant rotation (no smoothing)
|
||||
this.CurrentPitch = this.TargetPitch;
|
||||
this.CurrentYaw = this.TargetYaw;
|
||||
this.CameraState.CurrentPitch = this.CameraState.TargetPitch;
|
||||
this.CameraState.CurrentYaw = this.CameraState.TargetYaw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,8 +113,8 @@ export class AC_Camera extends ActorComponent {
|
|||
*/
|
||||
public GetCameraRotation(): { Pitch: Float; Yaw: Float } {
|
||||
return {
|
||||
Pitch: this.CurrentPitch,
|
||||
Yaw: this.CurrentYaw,
|
||||
Pitch: this.CameraState.CurrentPitch,
|
||||
Yaw: this.CameraState.CurrentYaw,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +125,7 @@ export class AC_Camera extends ActorComponent {
|
|||
* @pure true
|
||||
*/
|
||||
public IsCameraRotating(): boolean {
|
||||
return this.InputMagnitude > 0.01;
|
||||
return this.CameraState.InputMagnitude > 0.01;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -156,143 +165,49 @@ export class AC_Camera extends ActorComponent {
|
|||
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` +
|
||||
`Sensitivity: ${SystemLibrary.IsValid(this.InputDeviceComponent) && this.InputDeviceComponent.IsGamepad() ? this.CameraSettings.GamepadSensitivity : this.CameraSettings.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'}`
|
||||
`Smoothing: ${this.CameraSettings.SmoothingSpeed}\n` +
|
||||
`Invert Y: ${this.CameraSettings.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)
|
||||
* Camera configuration settings
|
||||
* Controls sensitivity, limits, and smoothing behavior
|
||||
* @category Camera Config
|
||||
* @instanceEditable true
|
||||
* @default 100.0
|
||||
*/
|
||||
private readonly MouseSensitivity: Float = 100.0;
|
||||
public readonly CameraSettings: S_CameraSettings = {
|
||||
MouseSensitivity: 100.0,
|
||||
GamepadSensitivity: 150.0,
|
||||
InvertYAxis: false,
|
||||
PitchMin: -89.0,
|
||||
PitchMax: 89.0,
|
||||
SmoothingSpeed: 20.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()
|
||||
* Current camera rotation state
|
||||
* Tracks current, target, and input data
|
||||
* @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;
|
||||
private CameraState: S_CameraState = {
|
||||
CurrentPitch: 0.0,
|
||||
CurrentYaw: 0.0,
|
||||
TargetPitch: 0.0,
|
||||
TargetYaw: 0.0,
|
||||
LastInputDelta: new Vector(0, 0, 0),
|
||||
InputMagnitude: 0.0,
|
||||
};
|
||||
|
||||
/**
|
||||
* System initialization state flag
|
||||
|
|
|
|||
BIN
Content/Camera/Components/AC_Camera.uasset (Stored with Git LFS)
BIN
Content/Camera/Components/AC_Camera.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -0,0 +1,12 @@
|
|||
// Camera/Structs/S_CameraSettings.ts
|
||||
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
|
||||
export interface S_CameraSettings {
|
||||
MouseSensitivity: Float;
|
||||
GamepadSensitivity: Float;
|
||||
InvertYAxis: boolean;
|
||||
PitchMin: Float;
|
||||
PitchMax: Float;
|
||||
SmoothingSpeed: Float;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,13 @@
|
|||
// Camera/Structs/S_CameraState.ts
|
||||
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import type { Vector } from '#root/UE/Vector.ts';
|
||||
|
||||
export interface S_CameraState {
|
||||
CurrentPitch: Float;
|
||||
CurrentYaw: Float;
|
||||
TargetPitch: Float;
|
||||
TargetYaw: Float;
|
||||
LastInputDelta: Vector;
|
||||
InputMagnitude: Float;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -10,7 +10,6 @@
|
|||
- **Deterministic rotation:** Математически предсказуемое поведение камеры
|
||||
- **Smooth interpolation:** Плавное движение без потери отзывчивости
|
||||
- **Pitch constraints:** Строгие ограничения вертикального поворота, свободное горизонтальное вращение
|
||||
- **Flat architecture:** Прямой доступ к переменным без промежуточных структур
|
||||
|
||||
## Основной компонент
|
||||
|
||||
|
|
@ -21,19 +20,12 @@
|
|||
- Применение pitch limits (-89°/+89°) с free yaw rotation
|
||||
- Интеграция с Input Device detection для автоматического switching
|
||||
|
||||
**Архитектурные изменения:**
|
||||
- Удалены структуры `S_CameraSettings` и `S_CameraState`
|
||||
- Все переменные теперь напрямую в компоненте
|
||||
- Настройки защищены модификатором `private readonly`
|
||||
- Добавлен `GetTestData()` для доступа к настройкам в тестах
|
||||
|
||||
**Ключевые функции:**
|
||||
- `ProcessLookInput()` - Обработка look input с device-aware sensitivity
|
||||
- `UpdateCameraRotation()` - Smooth interpolation к target rotation
|
||||
- `GetCameraRotation()` - Получение current camera angles для SpringArm
|
||||
- `IsCameraRotating()` - Проверка активности camera input
|
||||
- `InitializeCameraSystem()` - Инициализация с Input Device integration
|
||||
- `GetTestData()` - Доступ к настройкам для тестирования
|
||||
|
||||
**Input processing flow:**
|
||||
```typescript
|
||||
|
|
@ -41,122 +33,88 @@ ProcessLookInput() →
|
|||
Device Detection (Mouse vs Gamepad) →
|
||||
Apply appropriate sensitivity →
|
||||
Calculate target rotation with pitch limits →
|
||||
Update internal state variables
|
||||
Update cached state
|
||||
|
||||
UpdateCameraRotation() →
|
||||
FInterpTo towards target →
|
||||
Update current rotation state
|
||||
```
|
||||
|
||||
## Система конфигурации
|
||||
|
||||
### Camera Settings (Instance Editable)
|
||||
Все настройки теперь являются `private readonly` переменными компонента:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Mouse sensitivity: 100.0
|
||||
* Higher values = faster camera movement with mouse
|
||||
* Typical range: 50.0 (slow) - 200.0 (fast)
|
||||
*/
|
||||
private readonly MouseSensitivity: Float = 100.0;
|
||||
|
||||
/**
|
||||
* Gamepad sensitivity: 150.0
|
||||
* Higher than mouse to compensate for analog stick
|
||||
* Typical range: 100.0 (slow) - 300.0 (fast)
|
||||
*/
|
||||
private readonly GamepadSensitivity: Float = 150.0;
|
||||
|
||||
/**
|
||||
* Y-axis inversion: false
|
||||
* When true, up input rotates camera down
|
||||
*/
|
||||
private readonly InvertYAxis: boolean = false;
|
||||
|
||||
/**
|
||||
* Minimum pitch: -89.0°
|
||||
* Prevents gimbal lock at -90°
|
||||
*/
|
||||
private readonly PitchMin: Float = -89.0;
|
||||
|
||||
/**
|
||||
* Maximum pitch: 89.0°
|
||||
* Prevents gimbal lock at +90°
|
||||
*/
|
||||
private readonly PitchMax: Float = 89.0;
|
||||
|
||||
/**
|
||||
* Smoothing speed: 20.0
|
||||
* Higher = more responsive, less smooth
|
||||
* Set to 0 for instant rotation
|
||||
* Typical range: 10.0 (smooth) - 30.0 (responsive)
|
||||
*/
|
||||
private readonly SmoothingSpeed: Float = 20.0;
|
||||
```
|
||||
|
||||
### Camera State (Private Variables)
|
||||
Внутреннее состояние камеры хранится в приватных переменных:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Current rotation (for rendering)
|
||||
* Smoothly interpolates towards target
|
||||
*/
|
||||
private CurrentPitch: Float = 0;
|
||||
private CurrentYaw: Float = 0;
|
||||
|
||||
/**
|
||||
* Target rotation (from input)
|
||||
* Updated by ProcessLookInput()
|
||||
*/
|
||||
private TargetPitch: Float = 0;
|
||||
private TargetYaw: Float = 0;
|
||||
|
||||
/**
|
||||
* Input tracking (for debugging)
|
||||
*/
|
||||
private LastInputDelta = new Vector(0, 0, 0);
|
||||
private InputMagnitude: Float = 0;
|
||||
```
|
||||
|
||||
## Система чувствительности
|
||||
|
||||
### Device-aware Sensitivity
|
||||
```typescript
|
||||
// Автоматическое определение чувствительности
|
||||
const sensitivity = this.InputDeviceComponent.IsGamepad()
|
||||
? this.GamepadSensitivity // 150.0
|
||||
: this.MouseSensitivity // 100.0
|
||||
? this.CameraSettings.GamepadSensitivity // 150.0
|
||||
: this.CameraSettings.MouseSensitivity // 100.0
|
||||
```
|
||||
|
||||
### Sensitivity Values
|
||||
```typescript
|
||||
CameraSettings: S_CameraSettings = {
|
||||
MouseSensitivity: 100.0, // Оптимально для точности мыши
|
||||
GamepadSensitivity: 150.0, // Выше для компенсации analog stick
|
||||
InvertYAxis: false, // Стандартное поведение
|
||||
PitchMin: -89.0, // Почти вертикально вниз
|
||||
PitchMax: 89.0, // Почти вертикально вверх
|
||||
SmoothingSpeed: 20.0 // Баланс между responsive и smooth
|
||||
}
|
||||
```
|
||||
|
||||
### Y-axis Inversion
|
||||
```typescript
|
||||
// Инверсия Y оси при включении
|
||||
const invertMultiplier = this.InvertYAxis ? -1.0 : 1.0
|
||||
const invertMultiplier = this.CameraSettings.InvertYAxis ? -1.0 : 1.0
|
||||
const targetPitch = currentPitch - inputDeltaY * sensitivity * invertMultiplier * deltaTime
|
||||
```
|
||||
|
||||
## Структуры данных
|
||||
|
||||
### S_CameraSettings
|
||||
```typescript
|
||||
interface S_CameraSettings {
|
||||
MouseSensitivity: Float // Чувствительность мыши (100.0)
|
||||
GamepadSensitivity: Float // Чувствительность геймпада (150.0)
|
||||
InvertYAxis: boolean // Инверсия Y оси (false)
|
||||
PitchMin: Float // Минимальный pitch (-89.0°)
|
||||
PitchMax: Float // Максимальный pitch (89.0°)
|
||||
SmoothingSpeed: Float // Скорость сглаживания (20.0)
|
||||
}
|
||||
```
|
||||
|
||||
### S_CameraState
|
||||
```typescript
|
||||
interface S_CameraState {
|
||||
CurrentPitch: Float // Текущий pitch для отображения
|
||||
CurrentYaw: Float // Текущий yaw для отображения
|
||||
TargetPitch: Float // Целевой pitch для interpolation
|
||||
TargetYaw: Float // Целевой yaw для interpolation
|
||||
LastInputDelta: Vector // Последний input для debugging
|
||||
InputMagnitude: Float // Величина input для IsCameraRotating()
|
||||
}
|
||||
```
|
||||
|
||||
## Система ограничений
|
||||
|
||||
### Pitch Limitations
|
||||
```typescript
|
||||
// Строгие ограничения вертикального поворота
|
||||
this.TargetPitch = MathLibrary.ClampFloat(
|
||||
this.CameraState.TargetPitch = MathLibrary.ClampFloat(
|
||||
calculatedPitch,
|
||||
this.PitchMin, // -89.0°
|
||||
this.PitchMax // +89.0°
|
||||
this.CameraSettings.PitchMin, // -89.0°
|
||||
this.CameraSettings.PitchMax // +89.0°
|
||||
)
|
||||
```
|
||||
|
||||
### Free Yaw Rotation
|
||||
```typescript
|
||||
// Yaw rotation без ограничений
|
||||
this.TargetYaw = CalculateTargetYaw(
|
||||
this.TargetYaw,
|
||||
// Yaw rotation без ограничений - может достигать любых значений
|
||||
this.CameraState.TargetYaw = CalculateTargetYaw(
|
||||
this.CameraState.TargetYaw,
|
||||
InputDelta.X,
|
||||
DeltaTime
|
||||
) // Может быть любым значением: 0°, 360°, 720°, -180° и т.д.
|
||||
) // Результат может быть 0°, 360°, 720°, -180° и т.д.
|
||||
```
|
||||
|
||||
**Обоснование свободного Yaw:**
|
||||
|
|
@ -168,26 +126,27 @@ this.TargetYaw = CalculateTargetYaw(
|
|||
|
||||
### FInterpTo Implementation
|
||||
```typescript
|
||||
public UpdateCameraRotation(DeltaTime: Float): void {
|
||||
if (this.SmoothingSpeed > 0) {
|
||||
// Smooth interpolation к target rotation
|
||||
UpdateCameraRotation(DeltaTime: Float): void {
|
||||
if (this.CameraSettings.SmoothingSpeed > 0) {
|
||||
// Smooth mode - используем FInterpTo
|
||||
this.CurrentPitch = MathLibrary.FInterpTo(
|
||||
this.CurrentPitch,
|
||||
this.TargetPitch,
|
||||
this.CameraState.CurrentPitch = MathLibrary.FInterpTo(
|
||||
this.CameraState.CurrentPitch,
|
||||
this.CameraState.TargetPitch,
|
||||
DeltaTime,
|
||||
this.SmoothingSpeed // 20.0
|
||||
this.CameraSettings.SmoothingSpeed // 20.0
|
||||
)
|
||||
|
||||
this.CurrentYaw = MathLibrary.FInterpTo(
|
||||
this.CurrentYaw,
|
||||
this.TargetYaw,
|
||||
this.CameraState.CurrentYaw = MathLibrary.FInterpTo(
|
||||
this.CameraState.CurrentYaw,
|
||||
this.CameraState.TargetYaw,
|
||||
DeltaTime,
|
||||
this.SmoothingSpeed
|
||||
this.CameraSettings.SmoothingSpeed
|
||||
)
|
||||
} else {
|
||||
// Instant mode - прямое присваивание
|
||||
this.CurrentPitch = this.TargetPitch
|
||||
this.CurrentYaw = this.TargetYaw
|
||||
this.CameraState.CurrentPitch = this.CameraState.TargetPitch
|
||||
this.CameraState.CurrentYaw = this.CameraState.TargetYaw
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -201,73 +160,56 @@ public UpdateCameraRotation(DeltaTime: Float): void {
|
|||
## Производительность
|
||||
|
||||
### Оптимизации
|
||||
- **Прямой доступ к переменным:** Отсутствие object property access overhead
|
||||
- **Cached device queries:** InputDeviceComponent.IsGamepad() вызывается один раз per frame
|
||||
- **Efficient math:** Minimal trigonometry, простые арифметические операции
|
||||
- **Separated state:** Target vs Current separation для smooth interpolation
|
||||
- **State separation:** Target vs Current separation для smooth interpolation
|
||||
- **Input magnitude caching:** Для IsCameraRotating() без дополнительных расчетов
|
||||
|
||||
### Benchmarks
|
||||
- **ProcessLookInput:** <0.008ms per call (улучшение за счет flat structure)
|
||||
- **UpdateCameraRotation:** <0.015ms per call (FInterpTo x2)
|
||||
- **GetCameraRotation:** <0.0005ms per call (прямой доступ к переменным)
|
||||
- **IsCameraRotating:** <0.0005ms per call (cached magnitude)
|
||||
- **Memory footprint:** ~120 байт на компонент (уменьшение за счет удаления структур)
|
||||
- **ProcessLookInput:** <0.01ms per call
|
||||
- **UpdateCameraRotation:** <0.02ms per call (FInterpTo x2)
|
||||
- **GetCameraRotation:** <0.001ms per call (cached access)
|
||||
- **IsCameraRotating:** <0.001ms per call (cached magnitude)
|
||||
- **Memory footprint:** ~150 байт на компонент
|
||||
|
||||
### Performance characteristics
|
||||
- **Deterministic timing:** Поведение не зависит от framerate
|
||||
- **Delta time dependent:** Корректное scaling по времени
|
||||
- **No allocations:** Все операции работают с existing variables
|
||||
- **No allocations:** Все операции работают с existing state
|
||||
- **Minimal branching:** Эффективное выполнение на современных CPU
|
||||
- **Improved cache locality:** Переменные расположены последовательно в памяти
|
||||
|
||||
## Система тестирования
|
||||
|
||||
### GetTestData() для доступа к настройкам
|
||||
```typescript
|
||||
/**
|
||||
* Возвращает настройки камеры для тестирования
|
||||
* Обеспечивает read-only доступ к private readonly переменным
|
||||
*/
|
||||
public GetTestData(): {
|
||||
MouseSensitivity: Float;
|
||||
GamepadSensitivity: Float;
|
||||
PitchMin: Float;
|
||||
PitchMax: Float;
|
||||
}
|
||||
```
|
||||
|
||||
### Тестовые сценарии
|
||||
|
||||
**FT_CameraInitialization**
|
||||
### FT_CameraInitialization
|
||||
**Проверяет базовую инициализацию:**
|
||||
- Корректность установки Input Device reference
|
||||
- Initial state (0,0) rotation после инициализации
|
||||
- IsCameraRotating() returns false изначально
|
||||
- GetTestData() возвращает корректные default values
|
||||
|
||||
**FT_CameraRotation**
|
||||
- Positive X input увеличивает Yaw
|
||||
- Positive Y input уменьшает Pitch (inverted by default)
|
||||
### FT_CameraRotation
|
||||
**Тестирует rotation calculations:**
|
||||
- Positive X input увеличивает Yaw (Mario Odyssey behavior)
|
||||
- Positive Y input уменьшает Pitch (inverted by default в input)
|
||||
- Rotation accumulation при multiple inputs
|
||||
- Zero input maintains current rotation
|
||||
|
||||
**FT_CameraLimits**
|
||||
### FT_CameraLimits
|
||||
**Валидирует pitch/yaw constraints:**
|
||||
- Pitch clamping в диапазоне [-89°, +89°]
|
||||
- Free yaw rotation (может превышать ±360°)
|
||||
- Boundary behavior на limit edges
|
||||
- GetTestData() возвращает корректные PitchMin/Max
|
||||
|
||||
**FT_CameraSensitivity**
|
||||
- Корректность loading sensitivity из GetTestData()
|
||||
### FT_CameraSensitivity
|
||||
**Проверяет device-aware sensitivity:**
|
||||
- Корректность loading sensitivity settings
|
||||
- Input processing produces rotation changes
|
||||
- IsCameraRotating() logic с active/inactive input
|
||||
- Device-aware sensitivity switching
|
||||
|
||||
**FT_CameraSmoothing**
|
||||
### FT_CameraSmoothing
|
||||
**Тестирует smooth interpolation:**
|
||||
- Target vs Current rotation separation
|
||||
- Progressive movement к target over multiple frames
|
||||
- Convergence к target после достаточных updates
|
||||
- SmoothingSpeed = 0 дает instant rotation
|
||||
|
||||
## Интеграция с системами
|
||||
|
||||
|
|
@ -276,8 +218,8 @@ public GetTestData(): {
|
|||
// Device-aware sensitivity switching
|
||||
const sensitivity = SystemLibrary.IsValid(this.InputDeviceComponent) &&
|
||||
this.InputDeviceComponent.IsGamepad()
|
||||
? this.GamepadSensitivity
|
||||
: this.MouseSensitivity
|
||||
? this.CameraSettings.GamepadSensitivity
|
||||
: this.CameraSettings.MouseSensitivity
|
||||
```
|
||||
|
||||
### С Main Character (BP_MainCharacter)
|
||||
|
|
@ -294,18 +236,22 @@ this.GetController().SetControlRotation(
|
|||
|
||||
### С Debug HUD System
|
||||
```typescript
|
||||
// Debug page для camera information
|
||||
UpdateCameraPage(): void {
|
||||
this.DebugHUDComponent.UpdatePageContent(
|
||||
this.DebugPageID,
|
||||
`Current Device: ${this.GetCurrentInputDevice()}\n` +
|
||||
`Sensitivity: ${this.GetCurrentSensitivity()}\n` +
|
||||
`Pitch: ${this.GetCameraRotation().Pitch}°\n` +
|
||||
`Yaw: ${this.GetCameraRotation().Yaw}°\n` +
|
||||
`Is Rotating: ${this.IsCameraRotating() ? 'Yes' : 'No'}\n` +
|
||||
`Smoothing: ${this.GetTestData().SmoothingSpeed}\n` + // Потребуется добавить в GetTestData()
|
||||
`Invert Y: ${this.InvertYAxis ? 'Yes' : 'No'}`
|
||||
)
|
||||
// Новая debug page для camera information
|
||||
UpdateCameraPage(Page: S_DebugPage): S_DebugPage {
|
||||
return {
|
||||
PageID: Page.PageID,
|
||||
Title: Page.Title,
|
||||
Content:
|
||||
`Current Device: ${this.GetCurrentInputDevice()}\n` +
|
||||
`Sensitivity: ${this.GetCurrentSensitivity()}\n` +
|
||||
`Pitch: ${this.CameraComponent.GetCameraRotation().Pitch}°\n` +
|
||||
`Yaw: ${this.CameraComponent.GetCameraRotation().Yaw}°\n` +
|
||||
`Is Rotating: ${this.CameraComponent.IsCameraRotating() ? 'Yes' : 'No'}\n` +
|
||||
`Smoothing: ${this.CameraComponent.CameraSettings.SmoothingSpeed}\n` +
|
||||
`Invert Y: ${this.CameraComponent.CameraSettings.InvertYAxis ? 'Yes' : 'No'}`,
|
||||
IsVisible: Page.IsVisible,
|
||||
UpdateFunction: Page.UpdateFunction
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -319,8 +265,7 @@ ProcessLookInput(InputDelta: Vector, DeltaTime: Float): void
|
|||
```
|
||||
**Описание:** Обрабатывает look input с device-aware sensitivity
|
||||
**Параметры:** InputDelta (X=Yaw, Y=Pitch), DeltaTime для frame-rate independence
|
||||
**Эффекты:** Обновляет TargetPitch/TargetYaw, применяет pitch limits
|
||||
**Performance:** <0.008ms per call
|
||||
**Эффекты:** Обновляет TargetPitch/TargetYaw, применяет pitch limits
|
||||
|
||||
#### UpdateCameraRotation()
|
||||
```typescript
|
||||
|
|
@ -328,8 +273,7 @@ UpdateCameraRotation(DeltaTime: Float): void
|
|||
```
|
||||
**Описание:** Smooth interpolation к target rotation using FInterpTo
|
||||
**Когда вызывать:** EventTick в main character каждый frame
|
||||
**Эффекты:** Обновляет CurrentPitch/CurrentYaw для rendering
|
||||
**Performance:** <0.015ms per call
|
||||
**Эффекты:** Обновляет CurrentPitch/CurrentYaw для rendering
|
||||
|
||||
#### GetCameraRotation()
|
||||
```typescript
|
||||
|
|
@ -337,7 +281,7 @@ GetCameraRotation(): { Pitch: Float; Yaw: Float }
|
|||
```
|
||||
**Описание:** Возвращает current camera rotation для SpringArm
|
||||
**Возвращает:** Object с Pitch и Yaw values
|
||||
**Performance:** <0.0005ms (прямой доступ к переменным)
|
||||
**Performance:** <0.001ms (cached state access)
|
||||
|
||||
#### IsCameraRotating()
|
||||
```typescript
|
||||
|
|
@ -349,29 +293,27 @@ IsCameraRotating(): boolean
|
|||
|
||||
#### InitializeCameraSystem()
|
||||
```typescript
|
||||
InitializeCameraSystem(InputDeviceRef: AC_InputDevice, DebugComponentRef: AC_DebugHUD): void
|
||||
InitializeCameraSystem(InputDeviceRef: AC_InputDevice): void
|
||||
```
|
||||
**Описание:** Инициализирует camera system с device integration
|
||||
**Параметры:** InputDeviceRef для device-aware sensitivity, DebugComponentRef для debug output
|
||||
**Параметры:** InputDeviceRef для device-aware sensitivity
|
||||
**Когда вызывать:** EventBeginPlay в main character
|
||||
|
||||
#### GetTestData()
|
||||
```typescript
|
||||
GetTestData(): {
|
||||
MouseSensitivity: Float;
|
||||
GamepadSensitivity: Float;
|
||||
PitchMin: Float;
|
||||
PitchMax: Float;
|
||||
}
|
||||
```
|
||||
**Описание:** Возвращает настройки камеры для тестирования
|
||||
**Возвращает:** Object с основными настройками sensitivity и pitch limits
|
||||
**Use case:** Automated tests, validation, debugging
|
||||
**Note:** Не включает InvertYAxis и SmoothingSpeed (можно добавить при необходимости)
|
||||
|
||||
### Публичные свойства
|
||||
|
||||
#### InputDeviceComponent
|
||||
#### CameraSettings (Instance Editable)
|
||||
```typescript
|
||||
readonly CameraSettings: S_CameraSettings = {
|
||||
MouseSensitivity: 100.0, // Чувствительность мыши
|
||||
GamepadSensitivity: 150.0, // Чувствительность геймпада
|
||||
InvertYAxis: false, // Y-axis inversion
|
||||
PitchMin: -89.0, // Minimum pitch limit
|
||||
PitchMax: 89.0, // Maximum pitch limit
|
||||
SmoothingSpeed: 20.0 // FInterpTo speed
|
||||
}
|
||||
```
|
||||
|
||||
#### InputDeviceComponent (Public Reference)
|
||||
```typescript
|
||||
InputDeviceComponent: AC_InputDevice | null = null
|
||||
```
|
||||
|
|
@ -379,106 +321,56 @@ InputDeviceComponent: AC_InputDevice | null = null
|
|||
**Set by:** InitializeCameraSystem() при инициализации
|
||||
**Use case:** Automatic sensitivity switching based на active device
|
||||
|
||||
#### DebugHUDComponent
|
||||
```typescript
|
||||
DebugHUDComponent: AC_DebugHUD | null = null
|
||||
```
|
||||
**Описание:** Reference к Debug HUD component для отображения camera info
|
||||
**Set by:** InitializeCameraSystem() при инициализации
|
||||
**Use case:** Debug visualization, development tools
|
||||
|
||||
#### DebugPageID
|
||||
```typescript
|
||||
readonly DebugPageID: string = 'CameraInfo'
|
||||
```
|
||||
**Описание:** Идентификатор debug page для camera information
|
||||
**Use case:** Debug HUD page management
|
||||
|
||||
## Расширяемость
|
||||
|
||||
### Рекомендуемые улучшения GetTestData()
|
||||
|
||||
**Вариант 1: Полный доступ ко всем settings и state**
|
||||
```typescript
|
||||
public GetTestData(): {
|
||||
// Settings
|
||||
MouseSensitivity: Float;
|
||||
GamepadSensitivity: Float;
|
||||
InvertYAxis: boolean;
|
||||
PitchMin: Float;
|
||||
PitchMax: Float;
|
||||
SmoothingSpeed: Float;
|
||||
// State
|
||||
CurrentPitch: Float;
|
||||
CurrentYaw: Float;
|
||||
TargetPitch: Float;
|
||||
TargetYaw: Float;
|
||||
InputMagnitude: Float;
|
||||
}
|
||||
```
|
||||
|
||||
**Вариант 2: Отдельные геттеры для разных категорий**
|
||||
```typescript
|
||||
public GetSettings(): CameraSettings { ... }
|
||||
public GetCurrentRotation(): { Pitch: Float; Yaw: Float } { ... }
|
||||
public GetTargetRotation(): { Pitch: Float; Yaw: Float } { ... }
|
||||
public GetInputState(): { LastDelta: Vector; Magnitude: Float } { ... }
|
||||
```
|
||||
|
||||
### Добавление новых устройств ввода
|
||||
1. Расширить device detection в `ProcessLookInput()`
|
||||
2. Добавить новые sensitivity settings как `private readonly` переменные
|
||||
2. Добавить новые sensitivity settings в `S_CameraSettings`
|
||||
3. Обновить logic в device-aware sensitivity calculation
|
||||
4. Расширить `GetTestData()` для включения новых settings
|
||||
|
||||
### Пример добавления Touch support:
|
||||
```typescript
|
||||
// 1. Add touch sensitivity setting
|
||||
private readonly TouchSensitivity: Float = 120.0;
|
||||
// 1. Extend settings
|
||||
interface S_CameraSettings {
|
||||
// ... existing settings
|
||||
TouchSensitivity: Float // Специально для touch input
|
||||
}
|
||||
|
||||
// 2. Update sensitivity logic
|
||||
const getSensitivity = (): Float => {
|
||||
if (!SystemLibrary.IsValid(this.InputDeviceComponent))
|
||||
return this.MouseSensitivity;
|
||||
|
||||
if (this.InputDeviceComponent.IsTouch())
|
||||
return this.TouchSensitivity;
|
||||
if (this.InputDeviceComponent.IsGamepad())
|
||||
return this.GamepadSensitivity;
|
||||
return this.MouseSensitivity;
|
||||
}
|
||||
|
||||
// 3. Extend GetTestData()
|
||||
public GetTestData() {
|
||||
return {
|
||||
// ... existing properties
|
||||
TouchSensitivity: this.TouchSensitivity
|
||||
};
|
||||
if (this.InputDeviceComponent.IsTouch()) return this.CameraSettings.TouchSensitivity
|
||||
if (this.InputDeviceComponent.IsGamepad()) return this.CameraSettings.GamepadSensitivity
|
||||
return this.CameraSettings.MouseSensitivity
|
||||
}
|
||||
```
|
||||
|
||||
### Новые camera modes
|
||||
- **Look-ahead camera:** Камера смотрит вперед по направлению движения
|
||||
- **Auto-follow mode:** Камера автоматически следует за target
|
||||
- **Cinematic mode:** Scripted camera movements для cutscenes
|
||||
- **Free-look toggle:** Переключение между attached и free camera
|
||||
|
||||
## Известные ограничения
|
||||
|
||||
### Текущие ограничения
|
||||
1. **GetTestData() неполный** - Не включает все settings (InvertYAxis, SmoothingSpeed)
|
||||
2. **No state access for tests** - Нет доступа к CurrentPitch/TargetYaw для детального тестирования
|
||||
3. **Single input source** - Обрабатывает только один input device за раз
|
||||
4. **No camera collision** - Камера может проваливаться через geometry
|
||||
5. **Fixed smoothing speed** - Одна скорость сглаживания для всех ситуаций
|
||||
1. **Single input source** - Обрабатывает только один input device за раз
|
||||
2. **No camera collision** - Камера может проваливаться через geometry
|
||||
3. **Fixed smoothing speed** - Одна скорость сглаживания для всех ситуаций
|
||||
4. **No camera shake** - Отсутствует system для screen shake effects
|
||||
|
||||
### Архитектурные ограничения
|
||||
1. **2D rotation only** - Только Pitch/Yaw, нет Roll support
|
||||
2. **Linear interpolation** - Простой FInterpTo без advanced easing
|
||||
3. **No prediction** - Отсутствует input prediction для reduce latency
|
||||
4. **Readonly settings** - Невозможно изменить sensitivity в runtime (можно убрать readonly при необходимости)
|
||||
4. **Single sensitivity per device** - Нет fine-tuning для разных game situations
|
||||
|
||||
## Планы развития
|
||||
## Планы развития (Stage 7+)
|
||||
|
||||
### Краткосрочные улучшения
|
||||
1. **Расширить GetTestData()** - Включить все settings и state variables
|
||||
2. **Camera collision system** - Custom collision detection для камеры
|
||||
3. **Adaptive smoothing** - Разная скорость сглаживания для different scenarios
|
||||
4. **Runtime settings** - Опция изменять sensitivity через меню настроек
|
||||
1. **Camera collision system** - Custom collision detection для камеры
|
||||
2. **Adaptive smoothing** - Разная скорость сглаживания для different scenarios
|
||||
3. **Camera shake integration** - Screen shake для impacts и explosions
|
||||
4. **Look-ahead prediction** - Камера anticipates movement direction
|
||||
|
||||
### Долгосрочные цели
|
||||
1. **Multiple camera modes** - Free-look, follow, cinematic modes
|
||||
|
|
@ -486,13 +378,33 @@ public GetTestData() {
|
|||
3. **Multi-input support** - Simultaneous mouse+gamepad support
|
||||
4. **Accessibility features** - Reduced motion, motion sickness mitigation
|
||||
|
||||
## Интеграционные точки
|
||||
|
||||
### С SpringArm Component
|
||||
- **SetControlRotation:** Camera angles применяются к SpringArm через Controller
|
||||
- **Lag settings:** SpringArm lag должен быть минимальным для responsive feel
|
||||
- **Collision detection:** SpringArm handles camera collision с препятствиями
|
||||
|
||||
### С Enhanced Input System
|
||||
- **Input Actions:** IA_Look action sends input to ProcessLookInput
|
||||
- **Input Mapping:** Different mappings для mouse и gamepad в IMC_Default
|
||||
- **Input buffering:** System может buffer input для smooth processing
|
||||
|
||||
### С Animation System
|
||||
- **Head tracking:** Character head может follow camera direction
|
||||
- **Look-at targets:** Animations могут use camera direction для natural posing
|
||||
- **State transitions:** Camera rotation может trigger animation states
|
||||
|
||||
## Файловая структура
|
||||
|
||||
```
|
||||
Content/
|
||||
├── Camera/
|
||||
│ ├── Components/
|
||||
│ │ └── AC_Camera.ts # Core camera logic (refactored)
|
||||
│ │ └── AC_Camera.ts # Core camera logic
|
||||
│ ├── Structs/
|
||||
│ │ ├── S_CameraSettings.ts # Configuration settings
|
||||
│ │ └── S_CameraState.ts # Runtime state data
|
||||
│ └── Tests/
|
||||
│ ├── FT_CameraInitialization.ts # Basic initialization
|
||||
│ ├── FT_CameraRotation.ts # Rotation calculations
|
||||
|
|
@ -500,25 +412,21 @@ Content/
|
|||
│ ├── FT_CameraSensitivity.ts # Device-aware sensitivity
|
||||
│ └── FT_CameraSmoothing.ts # Smooth interpolation
|
||||
├── Debug/
|
||||
│ └── Components/
|
||||
│ └── AC_DebugHUD.ts # Debug HUD integration
|
||||
│ ├── Enums/
|
||||
│ │ ├── E_DebugPageID.ts # CameraInfo page ID
|
||||
│ │ └── E_DebugUpdateFunction.ts # UpdateCameraPage function
|
||||
│ └── Tables/
|
||||
│ └── DT_DebugPages.ts # Camera debug page data
|
||||
└── Blueprints/
|
||||
└── BP_MainCharacter.ts # Integration point
|
||||
```
|
||||
|
||||
**Удаленные файлы после рефакторинга:**
|
||||
- `Camera/Structs/S_CameraSettings.ts` - Заменено на private readonly переменные
|
||||
- `Camera/Structs/S_CameraState.ts` - Заменено на private переменные
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Использование в коде
|
||||
```typescript
|
||||
// ✅ Хорошо - инициализация с обоими компонентами
|
||||
this.CameraComponent.InitializeCameraSystem(
|
||||
this.InputDeviceComponent,
|
||||
this.DebugHUDComponent
|
||||
)
|
||||
// ✅ Хорошо - инициализация с Input Device reference
|
||||
this.CameraComponent.InitializeCameraSystem(this.InputDeviceComponent)
|
||||
|
||||
// ✅ Хорошо - обработка input каждый frame
|
||||
this.CameraComponent.ProcessLookInput(inputVector, deltaTime)
|
||||
|
|
@ -526,20 +434,14 @@ this.CameraComponent.UpdateCameraRotation(deltaTime)
|
|||
|
||||
// ✅ Хорошо - применение к SpringArm через Controller
|
||||
const rotation = this.CameraComponent.GetCameraRotation()
|
||||
this.GetController().SetControlRotation(
|
||||
new Rotator(0, rotation.Pitch, rotation.Yaw)
|
||||
)
|
||||
this.GetController().SetControlRotation(new Rotator(0, rotation.Pitch, rotation.Yaw))
|
||||
|
||||
// ✅ Хорошо - доступ к настройкам в тестах
|
||||
const testData = this.CameraComponent.GetTestData()
|
||||
expect(testData.MouseSensitivity).toBe(100.0)
|
||||
|
||||
// ❌ Плохо - попытка прямого доступа к private переменным
|
||||
this.CameraComponent.CurrentPitch // Ошибка компиляции - private property
|
||||
// ❌ Плохо - использование без инициализации Input Device
|
||||
this.CameraComponent.ProcessLookInput(inputVector, deltaTime) // null reference
|
||||
|
||||
// ❌ Плохо - пропуск UpdateCameraRotation
|
||||
this.CameraComponent.ProcessLookInput(inputVector, deltaTime)
|
||||
// Забыли вызвать UpdateCameraRotation - no smoothing!
|
||||
// this.CameraComponent.UpdateCameraRotation(deltaTime) // Пропущено - no smoothing!
|
||||
```
|
||||
|
||||
### Рекомендации по настройке
|
||||
|
|
@ -549,70 +451,10 @@ this.CameraComponent.ProcessLookInput(inputVector, deltaTime)
|
|||
- **PitchMin/Max ±89°:** Предотвращает gimbal lock при ±90°
|
||||
|
||||
### Performance recommendations
|
||||
- GetCameraRotation() теперь еще быстрее благодаря прямому доступу к переменным
|
||||
- GetTestData() вызывайте только в тестах, не в production code
|
||||
- Кэшируйте результат GetCameraRotation() если используете multiple times per frame
|
||||
- Кэшируйте GetCameraRotation() result если используете multiple times per frame
|
||||
- Используйте IsCameraRotating() для conditional logic (animations, UI)
|
||||
- Настройте SmoothingSpeed based на target platform performance
|
||||
|
||||
## Миграция со структур на переменные
|
||||
|
||||
### Что изменилось
|
||||
**До рефакторинга:**
|
||||
```typescript
|
||||
// Доступ через структуры
|
||||
this.CameraSettings.MouseSensitivity
|
||||
this.CameraState.CurrentPitch
|
||||
|
||||
// Batch update возможен
|
||||
this.CameraSettings = newSettings;
|
||||
```
|
||||
|
||||
**После рефакторинга:**
|
||||
```typescript
|
||||
// Прямой доступ к переменным
|
||||
this.MouseSensitivity
|
||||
this.CurrentPitch
|
||||
|
||||
// Settings теперь readonly - изменения невозможны
|
||||
// this.MouseSensitivity = 200.0; // Ошибка компиляции
|
||||
```
|
||||
|
||||
### Преимущества новой архитектуры
|
||||
1. **Performance:** Прямой доступ быстрее чем object property lookup
|
||||
2. **Memory:** Меньше overhead без промежуточных структур (~30 байт экономии)
|
||||
3. **Simplicity:** Более плоская структура, легче понимать и поддерживать
|
||||
4. **Safety:** `readonly` настройки защищены от случайных изменений
|
||||
5. **Cache locality:** Переменные лежат последовательно в памяти
|
||||
|
||||
### Недостатки новой архитектуры
|
||||
1. **No batch updates:** Нельзя заменить все настройки одним присваиванием
|
||||
2. **More verbose GetTestData():** Нужно явно возвращать каждую переменную
|
||||
3. **Harder to serialize:** Нет единой структуры для save/load настроек
|
||||
|
||||
### Рекомендации по миграции
|
||||
Если вам нужна возможность изменять настройки в runtime:
|
||||
```typescript
|
||||
// Убрать readonly модификатор
|
||||
private MouseSensitivity: Float = 100.0; // Без readonly
|
||||
|
||||
// Добавить setter методы
|
||||
public SetMouseSensitivity(value: Float): void {
|
||||
this.MouseSensitivity = MathLibrary.ClampFloat(value, 10.0, 500.0);
|
||||
}
|
||||
|
||||
// Или добавить batch update метод
|
||||
public UpdateSettings(settings: {
|
||||
MouseSensitivity?: Float;
|
||||
GamepadSensitivity?: Float;
|
||||
// ...
|
||||
}): void {
|
||||
if (settings.MouseSensitivity !== undefined) {
|
||||
this.MouseSensitivity = settings.MouseSensitivity;
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
- Мониторьте InputMagnitude для debug плавности input detection
|
||||
|
||||
## Статистика использования
|
||||
|
||||
|
|
@ -628,97 +470,66 @@ ProcessLookInput(new Vector(0.8, 0.6, 0), 0.016) // Analog values 0-1 range
|
|||
ProcessLookInput(new Vector(15.0, 0, 0), 0.016) // Fast horizontal turns
|
||||
```
|
||||
|
||||
### Performance metrics (после рефакторинга)
|
||||
### Performance metrics (из тестов)
|
||||
- **Average ProcessLookInput calls per second:** 60 (every frame)
|
||||
- **GetCameraRotation overhead:** ~0.0005ms (улучшение на 50% благодаря прямому доступу)
|
||||
- **Memory per component:** ~120 байт (уменьшение на 20% без структур)
|
||||
- **Typical InputMagnitude range:** 0.0 - 5.0 (mouse), 0.0 - 1.0 (gamepad)
|
||||
- **Smoothing convergence time:** ~0.2-0.5 seconds to reach target
|
||||
- **Memory allocations per frame:** 0 (все operations используют existing variables)
|
||||
- **Memory allocations per frame:** 0 (все operations используют existing objects)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Частые проблемы
|
||||
1. **Camera не вращается**
|
||||
- Проверить InitializeCameraSystem() был вызван
|
||||
- Убедиться что ProcessLookInput() получает non-zero input
|
||||
- Проверить InputDeviceComponent reference установлен
|
||||
- Проверить InitializeCameraSystem() был вызван
|
||||
- Убедиться что ProcessLookInput() получает non-zero input
|
||||
- Проверить InputDeviceComponent reference установлен
|
||||
|
||||
2. **Jerky camera movement**
|
||||
- Убедиться что UpdateCameraRotation() вызывается каждый frame
|
||||
- Проверить SmoothingSpeed не слишком высокий (>50)
|
||||
- Валидировать DeltaTime передается корректно
|
||||
- Убедиться что UpdateCameraRotation() вызывается каждый frame
|
||||
- Проверить SmoothingSpeed не слишком высокий (>50)
|
||||
- Валидировать DeltaTime передается корректно
|
||||
|
||||
3. **Wrong sensitivity**
|
||||
- Проверить InputDeviceComponent.IsGamepad() returns correct value
|
||||
- Убедиться что device detection работает properly
|
||||
- Использовать GetTestData() для валидации настроек
|
||||
- Проверить InputDeviceComponent.IsGamepad() returns correct value
|
||||
- Убедиться что device detection работает properly
|
||||
- Валидировать CameraSettings values loaded correctly
|
||||
|
||||
4. **Pitch stuck at limits**
|
||||
- Проверить PitchMin/Max values через GetTestData()
|
||||
- Убедиться что ClampFloat работает корректно
|
||||
- Валидировать input inversion settings
|
||||
|
||||
5. **GetTestData() не возвращает все настройки**
|
||||
- Это ожидаемое поведение - текущая версия возвращает только sensitivity и pitch limits
|
||||
- Расширьте метод если нужен доступ к другим настройкам (InvertYAxis, SmoothingSpeed, state variables)
|
||||
|
||||
## Сравнение с предыдущей версией
|
||||
|
||||
### Структурные изменения
|
||||
| Аспект | До | После | Улучшение |
|
||||
|--------|-----|-------|-----------|
|
||||
| **Доступ к настройкам** | `this.CameraSettings.MouseSensitivity` | `this.MouseSensitivity` | ✅ Быстрее, проще |
|
||||
| **Доступ к состоянию** | `this.CameraState.CurrentPitch` | `this.CurrentPitch` | ✅ Быстрее, проще |
|
||||
| **Защита настроек** | Public struct, можно изменять | `private readonly` | ✅ Безопаснее |
|
||||
| **Memory overhead** | ~150 байт | ~120 байт | ✅ -20% |
|
||||
| **Performance** | 0.010ms ProcessLookInput | 0.008ms ProcessLookInput | ✅ +20% быстрее |
|
||||
| **Тестирование** | Прямой доступ к public structs | Через GetTestData() | ⚠️ Требует метод |
|
||||
| **Batch updates** | Возможен | Невозможен | ⚠️ Меньше гибкости |
|
||||
| **Serialization** | Легко (один struct) | Сложнее (много variables) | ⚠️ Больше кода |
|
||||
|
||||
### Когда использовать новую архитектуру
|
||||
✅ **Используйте прямые переменные когда:**
|
||||
- Performance критичен
|
||||
- Настройки не меняются в runtime
|
||||
- Простота и читаемость важнее гибкости
|
||||
- Нужна защита от случайных изменений
|
||||
|
||||
⚠️ **Рассмотрите возврат к структурам когда:**
|
||||
- Нужны batch updates настроек
|
||||
- Требуется serialization/deserialization
|
||||
- Настройки часто меняются в runtime
|
||||
- Нужно передавать настройки между компонентами
|
||||
- Проверить PitchMin/Max values в settings (-89/+89)
|
||||
- Убедиться что ClampFloat работает корректно
|
||||
- Валидировать input inversion settings
|
||||
|
||||
## Заключение
|
||||
|
||||
Camera System после рефакторинга представляет собой упрощенную, более производительную и защищенную систему управления камерой для 3D-платформера с сохранением всех ключевых функций.
|
||||
Camera System представляет собой отзывчивую и плавную систему управления камерой для 3D-платформера с поддержкой device-aware sensitivity switching и deterministic behavior.
|
||||
|
||||
**Ключевые достижения рефакторинга:**
|
||||
- ✅ **Упрощенная архитектура:** Удалены промежуточные структуры, прямой доступ к переменным
|
||||
- ✅ **Улучшенная производительность:** +20% быстрее благодаря прямому доступу, -20% memory overhead
|
||||
- ✅ **Защищенные настройки:** `private readonly` предотвращает случайные изменения
|
||||
- ✅ **Сохранена функциональность:** Все core features работают идентично
|
||||
- ✅ **Тестируемость:** Добавлен GetTestData() для доступа к настройкам
|
||||
**Ключевые достижения:**
|
||||
- ✅ **Device-aware sensitivity:** Автоматическое переключение между mouse/gamepad settings
|
||||
- ✅ **Smooth interpolation:** Плавное движение без потери responsiveness
|
||||
- ✅ **Strict pitch limits:** Надежные ограничения -89°/+89° с free yaw rotation
|
||||
- ✅ **Deterministic behavior:** Математически предсказуемое поведение на всех платформах
|
||||
- ✅ **Comprehensive testing:** 5 автотестов покрывающих все core scenarios
|
||||
- ✅ **Debug HUD integration:** Полная интеграция с debug system для monitoring
|
||||
|
||||
**Готовность к production:**
|
||||
- Все автотесты требуют обновления для использования GetTestData()
|
||||
- Performance benchmarks показывают улучшение на 20%
|
||||
- Архитектура проще для понимания и поддержки
|
||||
- Memory footprint уменьшен на 20%
|
||||
- Deterministic behavior сохранен полностью
|
||||
- Все автотесты проходят успешно для boundary conditions и edge cases
|
||||
- Performance benchmarks соответствуют real-time требованиям (<0.02ms per frame)
|
||||
- Device detection интегрирована seamlessly с Input Device System
|
||||
- Math operations детерминированы и frame-rate independent
|
||||
- Memory management эффективен без allocations в runtime
|
||||
|
||||
**Архитектурные преимущества:**
|
||||
- Более плоская структура данных упрощает debugging
|
||||
- `readonly` settings обеспечивают compile-time safety
|
||||
- Прямой доступ к переменным улучшает cache locality
|
||||
- Меньше indirection означает меньше potential bugs
|
||||
- Extensible через добавление новых переменных и методов
|
||||
- Clean separation между input processing и rotation interpolation
|
||||
- Device-agnostic design позволяет легкое добавление новых input methods
|
||||
- State-based architecture с target/current separation для smooth movement
|
||||
- Integration-ready design для SpringArm, Animation, и других camera consumers
|
||||
- Extensible settings structure готова для future enhancements
|
||||
|
||||
**Рекомендации для дальнейшего развития:**
|
||||
1. **Расширить GetTestData()** для включения всех settings и state при необходимости
|
||||
2. **Добавить setter методы** если нужна runtime modification настроек
|
||||
3. **Реализовать serialization helpers** если нужно save/load настроек
|
||||
4. **Обновить все тесты** для использования GetTestData() вместо прямого доступа
|
||||
**Performance characteristics:**
|
||||
- Zero allocation camera operations для 60+ FPS stability
|
||||
- Deterministic timing независимо от framerate variations
|
||||
- Efficient device queries через cached InputDeviceComponent references
|
||||
- Minimal CPU overhead благодаря optimized math operations
|
||||
- Scalable architecture ready для advanced camera features
|
||||
|
||||
Camera System готова к использованию в production и provides improved foundation для advanced camera mechanics в будущих этапах разработки платформера с лучшей производительностью и безопасностью.
|
||||
Camera System готова к использованию в production и provides solid foundation для advanced camera mechanics в будущих этапах разработки платформера.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export class FT_CameraLimits extends FunctionalTest {
|
|||
|
||||
// Test 1: Test upper pitch limit clamping
|
||||
const { PitchMin: pitchMin, PitchMax: pitchMax } =
|
||||
this.CameraComponent.GetTestData();
|
||||
this.CameraComponent.CameraSettings;
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
this.CameraComponent.ProcessLookInput(new Vector(0.0, -10.0, 0.0), 0.016);
|
||||
|
|
|
|||
BIN
Content/Camera/Tests/FT_CameraLimits.uasset (Stored with Git LFS)
BIN
Content/Camera/Tests/FT_CameraLimits.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -40,7 +40,7 @@ export class FT_CameraSensitivity extends FunctionalTest {
|
|||
|
||||
// Test 1: Verify sensitivity settings are loaded correctly
|
||||
const { MouseSensitivity: mouseSens, GamepadSensitivity: gamepadSens } =
|
||||
this.CameraComponent.GetTestData();
|
||||
this.CameraComponent.CameraSettings;
|
||||
|
||||
if (mouseSens > 0 && gamepadSens > 0) {
|
||||
// Test 2: Apply input and verify rotation occurs
|
||||
|
|
|
|||
BIN
Content/Camera/Tests/FT_CameraSensitivity.uasset (Stored with Git LFS)
BIN
Content/Camera/Tests/FT_CameraSensitivity.uasset (Stored with Git LFS)
Binary file not shown.
Loading…
Reference in New Issue