diff --git a/.eslintrc.js b/.eslintrc.js index e4fec64..a32c05a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -43,13 +43,6 @@ module.exports = { 'sibling', 'index', ], - 'pathGroups': [ - { - 'pattern': '#root/**', - 'group': 'internal', - 'position': 'before' - } - ], 'pathGroupsExcludedImportTypes': ['builtin'], 'newlines-between': 'never', 'alphabetize': { diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index 15bab68..84efefb 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -93,3 +93,6 @@ ConnectionType=USBOnly bUseManualIPAddress=False ManualIPAddress= + +[CoreRedirects] ++ClassRedirects=(OldName="/Script/TengriPlatformer.UTengriCollisionResolver",NewName="/Script/TengriPlatformer.TengriCollisionResolver") \ No newline at end of file diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini index df039ef..5e28d22 100644 --- a/Config/DefaultGame.ini +++ b/Config/DefaultGame.ini @@ -5,3 +5,5 @@ CommonButtonAcceptKeyHandling=TriggerClick [/Script/EngineSettings.GeneralProjectSettings] ProjectID=56CEA3524FAE49EC0DF6D8A5178FEC04 +CopyrightNotice=Request Games © All rights reserved + diff --git a/Content/Blueprints/BP_MainCharacter.ts b/Content/Blueprints/BP_MainCharacter.ts index 6ff2ce0..aea45cd 100644 --- a/Content/Blueprints/BP_MainCharacter.ts +++ b/Content/Blueprints/BP_MainCharacter.ts @@ -1,22 +1,23 @@ -// Blueprints/BP_MainCharacter.ts +// Content/Blueprints/BP_MainCharacter.ts -import { AC_Camera } from '#root/Camera/Components/AC_Camera.ts'; -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { IMC_Default } from '#root/Input/IMC_Default.ts'; -import { AC_Movement } from '#root/Movement/AC_Movement.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { CapsuleComponent } from '#root/UE/CapsuleComponent.ts'; -import { Cast } from '#root/UE/Cast.ts'; -import type { Controller } from '#root/UE/Controller.ts'; -import { EnhancedInputLocalPlayerSubsystem } from '#root/UE/EnhancedInputLocalPlayerSubsystem.ts'; -import type { Float } from '#root/UE/Float.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { Pawn } from '#root/UE/Pawn.ts'; -import type { PlayerController } from '#root/UE/PlayerController.ts'; -import { Rotator } from '#root/UE/Rotator.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import { Vector } from '#root/UE/Vector.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'; +import { AC_ToastSystem } from '/Content/Toasts/Components/AC_ToastSystem.ts'; +import { CapsuleComponent } from '/Content/UE/CapsuleComponent.ts'; +import { Cast } from '/Content/UE/Cast.ts'; +import type { Controller } from '/Content/UE/Controller.ts'; +import { EnhancedInputLocalPlayerSubsystem } from '/Content/UE/EnhancedInputLocalPlayerSubsystem.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import { MathLibrary } from '/Content/UE/MathLibrary.ts'; +import { Pawn } from '/Content/UE/Pawn.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'; /** * Main Character Blueprint @@ -123,15 +124,17 @@ export class BP_MainCharacter extends Pawn { return new Vector(vec1.X + vec2.X, vec1.Y + vec2.Y, 0); }; - this.CurrentMovementInput = CalculateResultMovementInputVector( - MathLibrary.GetRightVector( - this.GetControlRotation().roll, - 0, - this.GetControlRotation().yaw - ), - MathLibrary.GetForwardVector(0, 0, this.GetControlRotation().yaw), - ActionValueX, - ActionValueY + this.TengriMovement.SetInputVector( + CalculateResultMovementInputVector( + MathLibrary.GetRightVector( + this.GetControlRotation().roll, + 0, + this.GetControlRotation().yaw + ), + MathLibrary.GetForwardVector(0, 0, this.GetControlRotation().yaw), + ActionValueX, + ActionValueY + ) ); } @@ -139,7 +142,7 @@ export class BP_MainCharacter extends Pawn { * Reset movement input when move action is completed */ EnhancedInputActionMoveCompleted(): void { - this.CurrentMovementInput = new Vector(0, 0, 0); + this.TengriMovement.SetInputVector(new Vector(0, 0, 0)); } /** @@ -163,11 +166,6 @@ export class BP_MainCharacter extends Pawn { ); } - this.MovementComponent.InitializeMovementSystem( - this.CharacterCapsule, - this.DebugHUDComponent - ); - this.CameraComponent.InitializeCameraSystem( this.InputDeviceComponent, this.DebugHUDComponent @@ -196,13 +194,7 @@ export class BP_MainCharacter extends Pawn { ) ); - this.MovementComponent.ProcessMovementInput( - this.CurrentMovementInput, - DeltaTime - ); - if (this.ShowDebugInfo) { - this.MovementComponent.UpdateDebugPage(); this.InputDeviceComponent.UpdateDebugPage(); this.CameraComponent.UpdateDebugPage(); } @@ -230,6 +222,8 @@ export class BP_MainCharacter extends Pawn { */ ToastSystemComponent = new AC_ToastSystem(); + TengriMovement = new TengriMovementComponent(DA_TengriMovementConfig); + /** * Debug HUD system - displays movement parameters and performance metrics * @category Components @@ -242,12 +236,6 @@ export class BP_MainCharacter extends Pawn { */ CharacterCapsule = new CapsuleComponent(); - /** - * Core movement system component - handles deterministic 3D platformer movement - * @category Components - */ - MovementComponent = new AC_Movement(); - /** * Master debug toggle - controls all debug systems (HUD, toasts, visual debug) * @category Debug @@ -259,9 +247,4 @@ export class BP_MainCharacter extends Pawn { * Cached delta time from last tick - used for time-based calculations */ private DeltaTime: Float = 0.0; - - /** - * Current movement input vector - updated by input actions - */ - private CurrentMovementInput: Vector = new Vector(0, 0, 0); } diff --git a/Content/Blueprints/BP_MainCharacter.uasset b/Content/Blueprints/BP_MainCharacter.uasset index 27965de..93a5531 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:aaf06f52894eaf5bc235cb89d4b8795003fd32419238fcf1d3839a211f5ac452 -size 330903 +oid sha256:468a4d9767ccc437aa25469a4273d39cbafceb6ee7218624123098e7ce83c2f2 +size 314135 diff --git a/Content/Blueprints/BP_TengriGameMode.ts b/Content/Blueprints/BP_TengriGameMode.ts index 0f2e7d9..860d165 100644 --- a/Content/Blueprints/BP_TengriGameMode.ts +++ b/Content/Blueprints/BP_TengriGameMode.ts @@ -1,7 +1,7 @@ // Blueprints/BP_TengriGameMode.ts -import { BP_MainCharacter } from '#root/Blueprints/BP_MainCharacter.ts'; -import { GameModeBase } from '#root/UE/GameModeBase.ts'; +import { BP_MainCharacter } from '/Content/Blueprints/BP_MainCharacter.ts'; +import { GameModeBase } from '/Content/UE/GameModeBase.ts'; export class BP_TengriGameMode extends GameModeBase { DefaultPawnClass = BP_MainCharacter; diff --git a/Content/Blueprints/BP_TengriGameMode.uasset b/Content/Blueprints/BP_TengriGameMode.uasset index 8248a83..7c7fa64 100644 --- a/Content/Blueprints/BP_TengriGameMode.uasset +++ b/Content/Blueprints/BP_TengriGameMode.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43b3a252e77ad42d4baa923d891394cfff672a998b10e33c7cb65a0e526b1ece +oid sha256:6b9cfc12f01b594d6aac3e0e6b38ee7f49fe62e6869bc8705701e0a7ebc0e883 size 20378 diff --git a/Content/Camera/Components/AC_Camera.ts b/Content/Camera/AC_Camera.ts similarity index 95% rename from Content/Camera/Components/AC_Camera.ts rename to Content/Camera/AC_Camera.ts index e09bf48..206773f 100644 --- a/Content/Camera/Components/AC_Camera.ts +++ b/Content/Camera/AC_Camera.ts @@ -1,12 +1,12 @@ -// Camera/Components/AC_Camera.ts +// Content/Camera/Components/AC_Camera.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'; -import type { Float } from '#root/UE/Float.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import { Vector } from '#root/UE/Vector.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 diff --git a/Content/Camera/AC_Camera.uasset b/Content/Camera/AC_Camera.uasset new file mode 100644 index 0000000..4d3f9a1 --- /dev/null +++ b/Content/Camera/AC_Camera.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c82f7e8be8be6b6b7e5b5340cef44769f2d89fb99b4290a527252bdd32660d54 +size 328871 diff --git a/Content/Camera/Components/AC_Camera.uasset b/Content/Camera/Components/AC_Camera.uasset deleted file mode 100644 index 96e03e0..0000000 --- a/Content/Camera/Components/AC_Camera.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b257d148eeb8dd22ef4c766deeadca08f05ecf2a0ad408ab212d25a1e5f2fa8 -size 329176 diff --git a/Content/Camera/ManualTestingChecklist.md b/Content/Camera/ManualTestingChecklist.md deleted file mode 100644 index 20447aa..0000000 --- a/Content/Camera/ManualTestingChecklist.md +++ /dev/null @@ -1,139 +0,0 @@ -[//]: # (Camera/ManualTestingChecklist.md) - -# Camera System - Manual Testing Checklist - -## Тестовая среда -- **Персонаж:** BP_MainCharacter с ShowDebugInfo = true -- **Клавиши:** Tab (Toggle HUD), PageUp/PageDown (навигация), Home (Visual Debug) -- **Требования:** CameraComponent инициализирован - ---- - -## 1. Базовая инициализация - -### 1.1 Система запуска -- [ ] **Camera System** инициализируется без ошибок при старте уровня -- [ ] **Debug HUD Page 5** отображается как "Camera System" -- [ ] **Initial rotation** камеры установлена в (0°, 0°) -- [ ] **IsCameraRotating()** возвращает false при отсутствии input - -### 1.2 Интеграция с Input Device -- [ ] **Input Device Reference** корректно устанавливается при инициализации -- [ ] **Sensitivity switching** работает при смене устройства ввода -- [ ] **No console errors** при инициализации системы - ---- - -## 2. Управление мышью - -### 2.1 Базовое вращение мышью -- [ ] **Horizontal movement** мыши поворачивает камеру по Yaw -- [ ] **Vertical movement** мыши поворачивает камеру по Pitch -- [ ] **Smooth rotation** - нет рывков и заиканий -- [ ] **Mouse sensitivity 100.0** - отзывчивая но не слишком быстрая - -### 2.2 Ограничения вращения мышью -- [ ] **Pitch limits** - камера не поворачивается выше +89° и ниже -89° -- [ ] **Yaw freedom** - горизонтальное вращение без ограничений (360°+) -- [ ] **Smooth clamping** - плавное достижение пределов без резких остановок - ---- - -## 3. Управление геймпадом - -### 3.1 Базовое вращение стиком -- [ ] **Right stick horizontal** поворачивает камеру по Yaw -- [ ] **Right stick vertical** поворачивает камеру по Pitch -- [ ] **Gamepad sensitivity 150.0** - более высокая чувствительность чем мышь -- [ ] **Smooth deadzones** - нет дрожания в центральном положении - -### 3.2 Автоматическое переключение устройств -- [ ] **Mouse movement** автоматически переключает на Mouse sensitivity -- [ ] **Gamepad input** автоматически переключает на Gamepad sensitivity -- [ ] **Seamless transition** - переключение без рывков камеры - ---- - -## 4. Система сглаживания - -### 4.1 Smooth interpolation -- [ ] **SmoothingSpeed 20.0** - плавное движение камеры к цели -- [ ] **Progressive acceleration** - камера ускоряется к target rotation -- [ ] **Natural stop** - плавная остановка без overshooting - -### 4.2 Responsiveness vs Smoothness -- [ ] **Input lag** минимальный - камера реагирует мгновенно на input -- [ ] **Visual smoothness** - движение камеры визуально плавное -- [ ] **Consistent timing** - сглаживание работает стабильно при разных FPS - ---- - -## 5. Debug HUD Integration - -### 5.1 Camera Page (Page 5) -- [ ] **Current Device** отображает "Keyboard & Mouse" или "Gamepad" -- [ ] **Sensitivity** показывает текущее значение чувствительности (100.0 или 150.0) -- [ ] **Pitch** отображает текущий угол наклона (-89° до +89°) -- [ ] **Yaw** показывает текущий поворот (любые значения, включая >360°) -- [ ] **Is Rotating** показывает "Yes" при активном input, "No" при покое -- [ ] **Smoothing** отображает значение скорости сглаживания (20.0) -- [ ] **Invert Y** показывает "No" (по умолчанию false) - -### 5.2 Control hints -- [ ] **Keyboard controls** показывают "PageUp/PageDown - Navigate" -- [ ] **Gamepad controls** показывают "D-Pad Up/Down - Navigate" -- [ ] **Dynamic switching** подсказок при смене устройства - ---- - -## 6. Продвинутые функции - -### 6.1 Y-axis inversion -- [ ] **InvertYAxis = false** - стандартное поведение (mouse up = look up) -- [ ] **Inversion calculation** - корректная инверсия при включении -- [ ] **Both devices** - инверсия работает для мыши и геймпада - -### 6.2 Edge cases -- [ ] **Rapid input changes** - быстрые движения мыши обрабатываются корректно -- [ ] **Extreme rotations** - Yaw может достигать больших значений (1000°+) -- [ ] **Zero input** - IsCameraRotating() корректно возвращает false при InputMagnitude < 0.01 - ---- - -## 7. Performance - -### 7.1 Производительность -- [ ] **No FPS drops** при активном вращении камеры -- [ ] **Smooth 60+ FPS** во время интенсивного camera movement -- [ ] **No memory leaks** при длительном использовании - -### 7.2 System integration -- [ ] **Main Character** - камера интегрирована без ошибок -- [ ] **Debug HUD** - обновление camera page не влияет на производительность -- [ ] **Input Device** - смена устройства не вызывает лагов - ---- - -## 8. Функциональные триггеры - -### 8.1 Навигация Debug HUD -- [ ] **PageUp/PageDown** (keyboard) переключают страницы Debug HUD -- [ ] **D-Pad Up/Down** (gamepad) переключают страницы Debug HUD -- [ ] **Camera page** доступна и отображается корректно - -### 8.2 Visual Debug -- [ ] **F2** не влияет на camera system (нет связанного visual debug) -- [ ] **F1 Toggle HUD** скрывает/показывает camera debug info - ---- - -## Критерии прохождения -- [ ] Все camera controls отзывчивые и плавные -- [ ] Pitch limits строго соблюдаются (-89°/+89°) -- [ ] Yaw rotation свободное (без ограничений) -- [ ] Device detection и sensitivity switching работают автоматически -- [ ] Debug HUD показывает актуальную информацию о camera state -- [ ] Performance стабильная при любых camera movements -- [ ] No console errors или warnings в camera system - -**Примечание:** Система полностью deterministic - одинаковые input sequence должны давать одинаковые результаты на разных запусках. diff --git a/Content/Camera/TDD.md b/Content/Camera/TDD.md deleted file mode 100644 index dc1b500..0000000 --- a/Content/Camera/TDD.md +++ /dev/null @@ -1,724 +0,0 @@ -[//]: # (Camera/TDD.md) - -# Camera System - Техническая Документация - -## Обзор -Детерминированная система управления камерой для 3D-платформера с поддержкой множественных устройств ввода и плавным сглаживанием. Система обеспечивает отзывчивое управление камерой в стиле Super Mario Odyssey с автоматическим переключением чувствительности между мышью и геймпадом. - -## Архитектурные принципы -- **Device-aware sensitivity:** Автоматическое переключение чувствительности на основе активного устройства ввода -- **Deterministic rotation:** Математически предсказуемое поведение камеры -- **Smooth interpolation:** Плавное движение без потери отзывчивости -- **Pitch constraints:** Строгие ограничения вертикального поворота, свободное горизонтальное вращение -- **Flat architecture:** Прямой доступ к переменным без промежуточных структур - -## Основной компонент - -### AC_Camera (Camera System Component) -**Ответственности:** -- Обработка input от мыши и геймпада с device-aware чувствительностью -- Плавное сглаживание rotation с помощью FInterpTo -- Применение 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 -ProcessLookInput() → - Device Detection (Mouse vs Gamepad) → - Apply appropriate sensitivity → - Calculate target rotation with pitch limits → - Update internal state variables - -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 -``` - -### Y-axis Inversion -```typescript -// Инверсия Y оси при включении -const invertMultiplier = this.InvertYAxis ? -1.0 : 1.0 -const targetPitch = currentPitch - inputDeltaY * sensitivity * invertMultiplier * deltaTime -``` - -## Система ограничений - -### Pitch Limitations -```typescript -// Строгие ограничения вертикального поворота -this.TargetPitch = MathLibrary.ClampFloat( - calculatedPitch, - this.PitchMin, // -89.0° - this.PitchMax // +89.0° -) -``` - -### Free Yaw Rotation -```typescript -// Yaw rotation без ограничений -this.TargetYaw = CalculateTargetYaw( - this.TargetYaw, - InputDelta.X, - DeltaTime -) // Может быть любым значением: 0°, 360°, 720°, -180° и т.д. -``` - -**Обоснование свободного Yaw:** -- Позволяет непрерывное вращение без "jumps" при переходе 360°→0° -- Поддерживает rapid turning без artificial limits -- Упрощает математику interpolation (нет wrap-around логики) - -## Система сглаживания - -### FInterpTo Implementation -```typescript -public UpdateCameraRotation(DeltaTime: Float): void { - if (this.SmoothingSpeed > 0) { - // Smooth mode - используем FInterpTo - this.CurrentPitch = MathLibrary.FInterpTo( - this.CurrentPitch, - this.TargetPitch, - DeltaTime, - this.SmoothingSpeed // 20.0 - ) - - this.CurrentYaw = MathLibrary.FInterpTo( - this.CurrentYaw, - this.TargetYaw, - DeltaTime, - this.SmoothingSpeed - ) - } else { - // Instant mode - прямое присваивание - this.CurrentPitch = this.TargetPitch - this.CurrentYaw = this.TargetYaw - } -} -``` - -### Smoothing Speed Tuning -- **SmoothingSpeed = 20.0:** Оптимальный баланс responsive/smooth -- **Higher values (30+):** Более отзывчиво, менее гладко -- **Lower values (10-):** Более гладко, менее отзывчиво -- **Zero:** Instant movement без сглаживания - -## Производительность - -### Оптимизации -- **Прямой доступ к переменным:** Отсутствие object property access overhead -- **Cached device queries:** InputDeviceComponent.IsGamepad() вызывается один раз per frame -- **Efficient math:** Minimal trigonometry, простые арифметические операции -- **Separated state:** 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 байт на компонент (уменьшение за счет удаления структур) - -### Performance characteristics -- **Deterministic timing:** Поведение не зависит от framerate -- **Delta time dependent:** Корректное scaling по времени -- **No allocations:** Все операции работают с existing variables -- **Minimal branching:** Эффективное выполнение на современных CPU -- **Improved cache locality:** Переменные расположены последовательно в памяти - -## Система тестирования - -### GetTestData() для доступа к настройкам -```typescript -/** - * Возвращает настройки камеры для тестирования - * Обеспечивает read-only доступ к private readonly переменным - */ -public GetTestData(): { - MouseSensitivity: Float; - GamepadSensitivity: Float; - PitchMin: Float; - PitchMax: Float; -} -``` - -### Тестовые сценарии - -**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) -- Rotation accumulation при multiple inputs -- Zero input maintains current rotation - -**FT_CameraLimits** -- Pitch clamping в диапазоне [-89°, +89°] -- Free yaw rotation (может превышать ±360°) -- Boundary behavior на limit edges -- GetTestData() возвращает корректные PitchMin/Max - -**FT_CameraSensitivity** -- Корректность loading sensitivity из GetTestData() -- Input processing produces rotation changes -- IsCameraRotating() logic с active/inactive input -- Device-aware sensitivity switching - -**FT_CameraSmoothing** -- Target vs Current rotation separation -- Progressive movement к target over multiple frames -- Convergence к target после достаточных updates -- SmoothingSpeed = 0 дает instant rotation - -## Интеграция с системами - -### С Input Device System -```typescript -// Device-aware sensitivity switching -const sensitivity = SystemLibrary.IsValid(this.InputDeviceComponent) && - this.InputDeviceComponent.IsGamepad() - ? this.GamepadSensitivity - : this.MouseSensitivity -``` - -### С Main Character (BP_MainCharacter) -```typescript -// В EventTick - применение camera rotation к SpringArm -this.GetController().SetControlRotation( - new Rotator( - 0, // Roll всегда 0 для платформера - this.CameraComponent.GetCameraRotation().Pitch, - this.CameraComponent.GetCameraRotation().Yaw - ) -) -``` - -### С 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'}` - ) -} -``` - -## API Reference - -### Основные методы - -#### ProcessLookInput() -```typescript -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 - -#### UpdateCameraRotation() -```typescript -UpdateCameraRotation(DeltaTime: Float): void -``` -**Описание:** Smooth interpolation к target rotation using FInterpTo -**Когда вызывать:** EventTick в main character каждый frame -**Эффекты:** Обновляет CurrentPitch/CurrentYaw для rendering -**Performance:** <0.015ms per call - -#### GetCameraRotation() -```typescript -GetCameraRotation(): { Pitch: Float; Yaw: Float } -``` -**Описание:** Возвращает current camera rotation для SpringArm -**Возвращает:** Object с Pitch и Yaw values -**Performance:** <0.0005ms (прямой доступ к переменным) - -#### IsCameraRotating() -```typescript -IsCameraRotating(): boolean -``` -**Описание:** Проверяет наличие active camera input -**Возвращает:** True если InputMagnitude > 0.01 -**Use case:** Animations, UI hints, debug information - -#### InitializeCameraSystem() -```typescript -InitializeCameraSystem(InputDeviceRef: AC_InputDevice, DebugComponentRef: AC_DebugHUD): void -``` -**Описание:** Инициализирует camera system с device integration -**Параметры:** InputDeviceRef для device-aware sensitivity, DebugComponentRef для debug output -**Когда вызывать:** 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 -```typescript -InputDeviceComponent: AC_InputDevice | null = null -``` -**Описание:** Reference к Input Device component для device detection -**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` переменные -3. Обновить logic в device-aware sensitivity calculation -4. Расширить `GetTestData()` для включения новых settings - -### Пример добавления Touch support: -```typescript -// 1. Add touch sensitivity setting -private readonly TouchSensitivity: Float = 120.0; - -// 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 - }; -} -``` - -## Известные ограничения - -### Текущие ограничения -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. **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 при необходимости) - -## Планы развития - -### Краткосрочные улучшения -1. **Расширить GetTestData()** - Включить все settings и state variables -2. **Camera collision system** - Custom collision detection для камеры -3. **Adaptive smoothing** - Разная скорость сглаживания для different scenarios -4. **Runtime settings** - Опция изменять sensitivity через меню настроек - -### Долгосрочные цели -1. **Multiple camera modes** - Free-look, follow, cinematic modes -2. **Advanced interpolation** - Smooth damp, ease curves, spring damping -3. **Multi-input support** - Simultaneous mouse+gamepad support -4. **Accessibility features** - Reduced motion, motion sickness mitigation - -## Файловая структура - -``` -Content/ -├── Camera/ -│ ├── Components/ -│ │ └── AC_Camera.ts # Core camera logic (refactored) -│ └── Tests/ -│ ├── FT_CameraInitialization.ts # Basic initialization -│ ├── FT_CameraRotation.ts # Rotation calculations -│ ├── FT_CameraLimits.ts # Pitch/Yaw constraints -│ ├── FT_CameraSensitivity.ts # Device-aware sensitivity -│ └── FT_CameraSmoothing.ts # Smooth interpolation -├── Debug/ -│ └── Components/ -│ └── AC_DebugHUD.ts # Debug HUD integration -└── 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 каждый frame -this.CameraComponent.ProcessLookInput(inputVector, deltaTime) -this.CameraComponent.UpdateCameraRotation(deltaTime) - -// ✅ Хорошо - применение к SpringArm через Controller -const rotation = this.CameraComponent.GetCameraRotation() -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 - -// ❌ Плохо - пропуск UpdateCameraRotation -this.CameraComponent.ProcessLookInput(inputVector, deltaTime) -// Забыли вызвать UpdateCameraRotation - no smoothing! -``` - -### Рекомендации по настройке -- **MouseSensitivity 100.0:** Стандартное значение для большинства пользователей -- **GamepadSensitivity 150.0:** Компенсирует менее точный analog stick -- **SmoothingSpeed 20.0:** Баланс между responsive и smooth -- **PitchMin/Max ±89°:** Предотвращает gimbal lock при ±90° - -### Performance recommendations -- GetCameraRotation() теперь еще быстрее благодаря прямому доступу к переменным -- GetTestData() вызывайте только в тестах, не в production code -- Кэшируйте результат GetCameraRotation() если используете 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; - } - // ... -} -``` - -## Статистика использования - -### Типичные input patterns -```typescript -// Mouse movement (60% camera input) -ProcessLookInput(new Vector(2.5, -1.2, 0), 0.016) // Small, precise movements - -// Gamepad stick (35% camera input) -ProcessLookInput(new Vector(0.8, 0.6, 0), 0.016) // Analog values 0-1 range - -// Rapid camera turns (5% camera input) -ProcessLookInput(new Vector(15.0, 0, 0), 0.016) // Fast horizontal turns -``` - -### 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) - -## Troubleshooting - -### Частые проблемы -1. **Camera не вращается** - - Проверить InitializeCameraSystem() был вызван - - Убедиться что ProcessLookInput() получает non-zero input - - Проверить InputDeviceComponent reference установлен - -2. **Jerky camera movement** - - Убедиться что UpdateCameraRotation() вызывается каждый frame - - Проверить SmoothingSpeed не слишком высокий (>50) - - Валидировать DeltaTime передается корректно - -3. **Wrong sensitivity** - - Проверить InputDeviceComponent.IsGamepad() returns correct value - - Убедиться что device detection работает properly - - Использовать GetTestData() для валидации настроек - -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 -- Нужно передавать настройки между компонентами - -## Заключение - -Camera System после рефакторинга представляет собой упрощенную, более производительную и защищенную систему управления камерой для 3D-платформера с сохранением всех ключевых функций. - -**Ключевые достижения рефакторинга:** -- ✅ **Упрощенная архитектура:** Удалены промежуточные структуры, прямой доступ к переменным -- ✅ **Улучшенная производительность:** +20% быстрее благодаря прямому доступу, -20% memory overhead -- ✅ **Защищенные настройки:** `private readonly` предотвращает случайные изменения -- ✅ **Сохранена функциональность:** Все core features работают идентично -- ✅ **Тестируемость:** Добавлен GetTestData() для доступа к настройкам - -**Готовность к production:** -- Все автотесты требуют обновления для использования GetTestData() -- Performance benchmarks показывают улучшение на 20% -- Архитектура проще для понимания и поддержки -- Memory footprint уменьшен на 20% -- Deterministic behavior сохранен полностью - -**Архитектурные преимущества:** -- Более плоская структура данных упрощает debugging -- `readonly` settings обеспечивают compile-time safety -- Прямой доступ к переменным улучшает cache locality -- Меньше indirection означает меньше potential bugs -- Extensible через добавление новых переменных и методов - -**Рекомендации для дальнейшего развития:** -1. **Расширить GetTestData()** для включения всех settings и state при необходимости -2. **Добавить setter методы** если нужна runtime modification настроек -3. **Реализовать serialization helpers** если нужно save/load настроек -4. **Обновить все тесты** для использования GetTestData() вместо прямого доступа - -Camera System готова к использованию в production и provides improved foundation для advanced camera mechanics в будущих этапах разработки платформера с лучшей производительностью и безопасностью. diff --git a/Content/Camera/Tests/FT_CameraInitialization.ts b/Content/Camera/Tests/FT_CameraInitialization.ts deleted file mode 100644 index 61244e2..0000000 --- a/Content/Camera/Tests/FT_CameraInitialization.ts +++ /dev/null @@ -1,101 +0,0 @@ -// Camera/Tests/FT_CameraInitialization.ts - -import { AC_Camera } from '#root/Camera/Components/AC_Camera.ts'; -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; - -/** - * Functional Test: Camera System Initialization - * Tests basic camera initialization and device integration - * Validates initial state and component references - */ -export class FT_CameraInitialization extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test execution - validates camera initialization - * Tests default values, device integration, and state consistency - */ - EventStartTest(): void { - // Initialize dependencies - this.ToastSystemComponent.InitializeToastSystem(); - this.InputDeviceComponent.InitializeDeviceDetection( - this.ToastSystemComponent, - this.DebugHUDComponent - ); - - // Initialize camera system - this.CameraComponent.InitializeCameraSystem( - this.InputDeviceComponent, - this.DebugHUDComponent - ); - - // Validate initialization - if ( - this.CameraComponent.InputDeviceComponent === this.InputDeviceComponent - ) { - // Validate initial state - const { Pitch: pitch, Yaw: yaw } = - this.CameraComponent.GetCameraRotation(); - - if (pitch === 0.0 && yaw === 0.0) { - // Validate not rotating initially - if (!this.CameraComponent.IsCameraRotating()) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Camera should not be rotating initially' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Initial rotation should be 0,0 but got Pitch=${pitch}, Yaw=${yaw}` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Input device component reference not set correctly' - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Camera system component - component under test - * @category Components - */ - private CameraComponent = new AC_Camera(); - - /** - * Input device detection system - required for camera initialization - * @category Components - */ - private InputDeviceComponent = new AC_InputDevice(); - - /** - * Toast notification system - required for input device initialization - * @category Components - */ - private ToastSystemComponent = new AC_ToastSystem(); - - /** - * Debug HUD system - displays test status and parameters - * @category Components - */ - private DebugHUDComponent = new AC_DebugHUD(); -} diff --git a/Content/Camera/Tests/FT_CameraInitialization.uasset b/Content/Camera/Tests/FT_CameraInitialization.uasset deleted file mode 100644 index e8951ea..0000000 --- a/Content/Camera/Tests/FT_CameraInitialization.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60a5038b0b733393a0dd18f9eebfba6065f1511685e11422c31e81deb6bfc3ae -size 121517 diff --git a/Content/Camera/Tests/FT_CameraLimits.ts b/Content/Camera/Tests/FT_CameraLimits.ts deleted file mode 100644 index 57be982..0000000 --- a/Content/Camera/Tests/FT_CameraLimits.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Camera/Tests/FT_CameraLimits.ts - -import { AC_Camera } from '#root/Camera/Components/AC_Camera.ts'; -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -/** - * Functional Test: Camera Limits and Constraints - * Tests pitch limits (-89°/+89°) and free yaw rotation - * Validates clamping behavior and overflow handling - */ -export class FT_CameraLimits extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test execution - validates pitch/yaw limits - * Tests boundary conditions and clamping behavior - */ - EventStartTest(): void { - // Initialize system - this.ToastSystemComponent.InitializeToastSystem(); - this.InputDeviceComponent.InitializeDeviceDetection( - this.ToastSystemComponent, - this.DebugHUDComponent - ); - this.CameraComponent.InitializeCameraSystem( - this.InputDeviceComponent, - this.DebugHUDComponent - ); - - // Test 1: Test upper pitch limit clamping - const { PitchMin: pitchMin, PitchMax: pitchMax } = - this.CameraComponent.GetTestData(); - - for (let i = 0; i < 100; i++) { - this.CameraComponent.ProcessLookInput(new Vector(0.0, -10.0, 0.0), 0.016); - this.CameraComponent.UpdateCameraRotation(0.016); - } - - const rotation1Pitch = this.CameraComponent.GetCameraRotation().Pitch; - if (rotation1Pitch <= pitchMax + 0.1) { - // Test 2: Test lower pitch limit clamping - for (let i = 0; i < 200; i++) { - this.CameraComponent.ProcessLookInput( - new Vector(0.0, 10.0, 0.0), - 0.016 - ); - this.CameraComponent.UpdateCameraRotation(0.016); - } - - const rotation2Pitch = this.CameraComponent.GetCameraRotation().Pitch; - if (rotation2Pitch >= pitchMin - 0.1) { - // Test 3: Test free yaw rotation (no limits) - for (let i = 0; i < 100; i++) { - this.CameraComponent.ProcessLookInput( - new Vector(5.0, 0.0, 0.0), - 0.016 - ); - this.CameraComponent.UpdateCameraRotation(0.016); - } - - const rotation3Yaw = this.CameraComponent.GetCameraRotation().Yaw; - if (MathLibrary.abs(rotation3Yaw) >= 360.0) { - // Test 4: Test yaw can go negative - for (let i = 0; i < 200; i++) { - this.CameraComponent.ProcessLookInput( - new Vector(-5.0, 0.0, 0.0), - 0.016 - ); - this.CameraComponent.UpdateCameraRotation(0.016); - } - - const rotation4Yaw = this.CameraComponent.GetCameraRotation().Yaw; - if (rotation4Yaw <= -360.0) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Yaw should allow negative rotation beyond -360°, got ${rotation4Yaw}°` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Yaw should allow free rotation beyond 360°, got ${rotation3Yaw}°` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Pitch ${rotation2Pitch}° below minimum limit ${pitchMin}°` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Pitch ${rotation1Pitch}° exceeds maximum limit ${pitchMax}°` - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Camera system component - component under test - * @category Components - */ - private CameraComponent = new AC_Camera(); - - /** - * Input device detection system - required for camera initialization - * @category Components - */ - private InputDeviceComponent = new AC_InputDevice(); - - /** - * Toast notification system - required for input device initialization - * @category Components - */ - private ToastSystemComponent = new AC_ToastSystem(); - - /** - * Debug HUD system - displays test status and parameters - * @category Components - */ - private DebugHUDComponent = new AC_DebugHUD(); -} diff --git a/Content/Camera/Tests/FT_CameraLimits.uasset b/Content/Camera/Tests/FT_CameraLimits.uasset deleted file mode 100644 index 30b5525..0000000 --- a/Content/Camera/Tests/FT_CameraLimits.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b7b8b7424e5d1516b6043cb061f536099c9951f1b20aff31987c9d24456a3fe -size 252649 diff --git a/Content/Camera/Tests/FT_CameraRotation.ts b/Content/Camera/Tests/FT_CameraRotation.ts deleted file mode 100644 index e188556..0000000 --- a/Content/Camera/Tests/FT_CameraRotation.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Camera/Tests/FT_CameraRotation.ts - -import { AC_Camera } from '#root/Camera/Components/AC_Camera.ts'; -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -/** - * Functional Test: Camera Rotation Calculations - * Tests pitch/yaw calculations and rotation accumulation - * Validates Mario Odyssey-style camera behavior - */ -export class FT_CameraRotation extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test execution - validates rotation calculations - * Tests positive/negative input, accumulation, and axis behavior - */ - EventStartTest(): void { - // Initialize system - this.ToastSystemComponent.InitializeToastSystem(); - this.InputDeviceComponent.InitializeDeviceDetection( - this.ToastSystemComponent, - this.DebugHUDComponent - ); - this.CameraComponent.InitializeCameraSystem( - this.InputDeviceComponent, - this.DebugHUDComponent - ); - - // Test positive X input (should increase Yaw) - this.CameraComponent.ProcessLookInput(new Vector(1.0, 0.0, 0.0), 0.016); - this.CameraComponent.UpdateCameraRotation(0.016); - - const { Pitch: rotation1Pitch, Yaw: rotation1Yaw } = - this.CameraComponent.GetCameraRotation(); - - if (rotation1Yaw > 0) { - // Test positive Y input (should decrease Pitch due to inversion) - this.CameraComponent.ProcessLookInput(new Vector(0.0, 1.0, 0.0), 0.016); - this.CameraComponent.UpdateCameraRotation(0.016); - - const { Pitch: rotation2Pitch, Yaw: rotation2Yaw } = - this.CameraComponent.GetCameraRotation(); - - if (rotation2Pitch < rotation1Pitch) { - // Test accumulation - second positive X should increase Yaw further - this.CameraComponent.ProcessLookInput(new Vector(1.0, 0.0, 0.0), 0.016); - this.CameraComponent.UpdateCameraRotation(0.016); - - const { Pitch: rotation3Pitch, Yaw: rotation3Yaw } = - this.CameraComponent.GetCameraRotation(); - - if (rotation3Yaw > rotation2Yaw) { - // Test zero input maintains rotation - this.CameraComponent.ProcessLookInput( - new Vector(0.0, 0.0, 0.0), - 0.016 - ); - this.CameraComponent.UpdateCameraRotation(0.016); - - const { Pitch: rotation4Pitch, Yaw: rotation4Yaw } = - this.CameraComponent.GetCameraRotation(); - - if ( - MathLibrary.abs(rotation4Yaw - rotation3Yaw) <= 0.01 && - MathLibrary.abs(rotation4Pitch - rotation3Pitch) <= 0.01 - ) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Zero input should maintain current rotation' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Multiple inputs should accumulate rotation' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Positive Y input should decrease Pitch (inverted)' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Positive X input should increase Yaw' - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Camera system component - component under test - * @category Components - */ - private CameraComponent = new AC_Camera(); - - /** - * Input device detection system - required for camera initialization - * @category Components - */ - private InputDeviceComponent = new AC_InputDevice(); - - /** - * Toast notification system - required for input device initialization - * @category Components - */ - private ToastSystemComponent = new AC_ToastSystem(); - - /** - * Debug HUD system - displays test status and parameters - * @category Components - */ - private DebugHUDComponent = new AC_DebugHUD(); -} diff --git a/Content/Camera/Tests/FT_CameraRotation.uasset b/Content/Camera/Tests/FT_CameraRotation.uasset deleted file mode 100644 index e0ec624..0000000 --- a/Content/Camera/Tests/FT_CameraRotation.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b233526fd9e2e21cf40f75e28b8136f3e80148e165301ec3ada3fd333051d80 -size 200360 diff --git a/Content/Camera/Tests/FT_CameraSensitivity.ts b/Content/Camera/Tests/FT_CameraSensitivity.ts deleted file mode 100644 index 118b827..0000000 --- a/Content/Camera/Tests/FT_CameraSensitivity.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Camera/Tests/FT_CameraSensitivity.ts - -import { AC_Camera } from '#root/Camera/Components/AC_Camera.ts'; -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -/** - * Functional Test: Camera Sensitivity System - * Tests device-specific sensitivity and device detection integration - * Validates mouse vs gamepad sensitivity differences - */ -export class FT_CameraSensitivity extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test execution - validates sensitivity calculations - * Tests device detection and appropriate sensitivity application - */ - EventStartTest(): void { - // Initialize system - this.ToastSystemComponent.InitializeToastSystem(); - this.InputDeviceComponent.InitializeDeviceDetection( - this.ToastSystemComponent, - this.DebugHUDComponent - ); - this.CameraComponent.InitializeCameraSystem( - this.InputDeviceComponent, - this.DebugHUDComponent - ); - - // Test 1: Verify sensitivity settings are loaded correctly - const { MouseSensitivity: mouseSens, GamepadSensitivity: gamepadSens } = - this.CameraComponent.GetTestData(); - - if (mouseSens > 0 && gamepadSens > 0) { - // Test 2: Apply input and verify rotation occurs - this.CameraComponent.ProcessLookInput(new Vector(1.0, 0.0, 0.0), 0.016); - this.CameraComponent.UpdateCameraRotation(0.016); - - if (this.CameraComponent.GetCameraRotation().Yaw !== 0.0) { - // Test 3: Verify IsCameraRotating() works with input - this.CameraComponent.ProcessLookInput(new Vector(1.0, 1.0, 0.0), 0.016); - if (this.CameraComponent.IsCameraRotating()) { - // Test 4: Verify IsCameraRotating() resets with zero input - this.CameraComponent.ProcessLookInput( - new Vector(0.0, 0.0, 0.0), - 0.016 - ); - if (!this.CameraComponent.IsCameraRotating()) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'IsCameraRotating should return false with zero input' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'IsCameraRotating should return true with active input' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Input should produce rotation change' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Mouse and gamepad sensitivities should be different' - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Camera system component - component under test - * @category Components - */ - private CameraComponent = new AC_Camera(); - - /** - * Input device detection system - required for camera initialization - * @category Components - */ - private InputDeviceComponent = new AC_InputDevice(); - - /** - * Toast notification system - required for input device initialization - * @category Components - */ - private ToastSystemComponent = new AC_ToastSystem(); - - /** - * Debug HUD system - displays test status and parameters - * @category Components - */ - private DebugHUDComponent = new AC_DebugHUD(); -} diff --git a/Content/Camera/Tests/FT_CameraSensitivity.uasset b/Content/Camera/Tests/FT_CameraSensitivity.uasset deleted file mode 100644 index 105026a..0000000 --- a/Content/Camera/Tests/FT_CameraSensitivity.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90b98235c9cc24ad634845e610d8c270932c7d0c53eec5f0203267f39e5ca8fd -size 134147 diff --git a/Content/Camera/Tests/FT_CameraSmoothing.ts b/Content/Camera/Tests/FT_CameraSmoothing.ts deleted file mode 100644 index 43f40ec..0000000 --- a/Content/Camera/Tests/FT_CameraSmoothing.ts +++ /dev/null @@ -1,122 +0,0 @@ -// Camera/Tests/FT_CameraSmoothing.ts - -import { AC_Camera } from '#root/Camera/Components/AC_Camera.ts'; -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -/** - * Functional Test: Camera Smoothing System - * Tests smooth interpolation vs instant rotation modes - * Validates FInterpTo behavior and smoothing speed effects - */ -export class FT_CameraSmoothing extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test execution - validates smoothing behavior - * Tests instant vs smooth rotation and interpolation accuracy - */ - EventStartTest(): void { - // Initialize system - this.ToastSystemComponent.InitializeToastSystem(); - this.InputDeviceComponent.InitializeDeviceDetection( - this.ToastSystemComponent, - this.DebugHUDComponent - ); - this.CameraComponent.InitializeCameraSystem( - this.InputDeviceComponent, - this.DebugHUDComponent - ); - - // Test 1: Test smooth rotation behavior - this.CameraComponent.ProcessLookInput(new Vector(5.0, 0.0, 0.0), 0.016); - - // Before UpdateCameraRotation, current should still be 0 - if (this.CameraComponent.GetCameraRotation().Yaw === 0.0) { - // After one update, should be moving toward target but not reached - this.CameraComponent.UpdateCameraRotation(0.016); - const afterUpdateYaw = this.CameraComponent.GetCameraRotation().Yaw; - - if (afterUpdateYaw !== 0.0) { - // Test 2: Verify smoothing continues over multiple frames - this.CameraComponent.UpdateCameraRotation(0.016); - - if (this.CameraComponent.GetCameraRotation().Yaw > afterUpdateYaw) { - // Test 3: Test convergence to target after many updates - this.CameraComponent.ProcessLookInput( - new Vector(1.0, 0.0, 0.0), - 0.016 - ); - - // Run many update cycles - for (let i = 0; i < 100; i++) { - this.CameraComponent.UpdateCameraRotation(0.016); - } - - // Should have converged to target after many updates - if (this.CameraComponent.GetCameraRotation().Yaw !== 0.0) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Smoothing should eventually reach target rotation' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Smoothing should continue to approach target' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Rotation should start moving after UpdateCameraRotation' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Current rotation should be 0 before UpdateCameraRotation' - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Camera system component - component under test - * @category Components - */ - private CameraComponent = new AC_Camera(); - - /** - * Input device detection system - required for camera initialization - * @category Components - */ - private InputDeviceComponent = new AC_InputDevice(); - - /** - * Toast notification system - required for input device initialization - * @category Components - */ - private ToastSystemComponent = new AC_ToastSystem(); - - /** - * Debug HUD system - displays test status and parameters - * @category Components - */ - private DebugHUDComponent = new AC_DebugHUD(); -} diff --git a/Content/Camera/Tests/FT_CameraSmoothing.uasset b/Content/Camera/Tests/FT_CameraSmoothing.uasset deleted file mode 100644 index 0d2f78e..0000000 --- a/Content/Camera/Tests/FT_CameraSmoothing.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8fa665d53a4f864f56e3e0bd4f9717d6244ba794242ce7575ad465003fb12471 -size 130177 diff --git a/Content/Debug/Components/AC_DebugHUD.ts b/Content/Debug/Components/AC_DebugHUD.ts index abac009..221ff62 100644 --- a/Content/Debug/Components/AC_DebugHUD.ts +++ b/Content/Debug/Components/AC_DebugHUD.ts @@ -1,18 +1,18 @@ -// Debug/Components/AC_DebugHUD.ts +// Content/Debug/Components/AC_DebugHUD.ts -import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.ts'; -import { WBP_DebugHUD } from '#root/Debug/UI/WBP_DebugHUD.ts'; -import type { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { ActorComponent } from '#root/UE/ActorComponent.ts'; -import { CreateWidget } from '#root/UE/CteateWidget.ts'; -import { ESlateVisibility } from '#root/UE/ESlateVisibility.ts'; -import type { Float } from '#root/UE/Float.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import type { Text } from '#root/UE/Text.ts'; -import { UEArray } from '#root/UE/UEArray.ts'; -import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; +import type { S_DebugPage } from '/Content/Debug/Structs/S_DebugPage.ts'; +import { WBP_DebugHUD } from '/Content/Debug/UI/WBP_DebugHUD.ts'; +import type { AC_InputDevice } from '/Content/Input/Components/AC_InputDevice.ts'; +import type { AC_ToastSystem } from '/Content/Toasts/Components/AC_ToastSystem.ts'; +import { ActorComponent } from '/Content/UE/ActorComponent.ts'; +import { CreateWidget } from '/Content/UE/CteateWidget.ts'; +import { ESlateVisibility } from '/Content/UE/ESlateVisibility.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import type { Integer } from '/Content/UE/Integer.ts'; +import { SystemLibrary } from '/Content/UE/SystemLibrary.ts'; +import type { Text } from '/Content/UE/Text.ts'; +import { UEArray } from '/Content/UE/UEArray.ts'; +import { E_MessageType } from '/Content/UI/Enums/E_MessageType.ts'; /** * Debug HUD Controller Component diff --git a/Content/Debug/ManualTestingChecklist.md b/Content/Debug/ManualTestingChecklist.md deleted file mode 100644 index e2efe96..0000000 --- a/Content/Debug/ManualTestingChecklist.md +++ /dev/null @@ -1,41 +0,0 @@ -[//]: # (Debug/ManualTestingChecklist.md) - -# Debug System - Manual Testing Checklist - -## Тестовая среена -- **Персонаж:** BP_MainCharacter с ShowDebugInfo = true -- **Клавиши:** PageUp/PageDown, Tab, Home -- **Требования:** MovementComponent и InputDeviceComponentRef инициализированы - ---- - -## 1. Навигация между страницами - -### 1.1 Клавиатурное управление -- [ ] **PageDown** переходит к следующей странице (NextPage) -- [ ] **PageUp** переходит к предыдущей странице (PreviousPage) -- [ ] **Циклическая навигация** - с последней страницы на первую -- [ ] **Обратная навигация** - с первой страницы на последнюю - -### 1.2 Отображение навигации -- [ ] **Page counter** показывает "Page X/3" (где X - текущая страница) -- [ ] **Navigation text** отображает "PageUp/PageDown - Navigate" - ---- - -## 2. Toggle функциональность - -### 2.1 Debug HUD toggle -- [ ] **Tab** скрывает/показывает весь debug HUD -- [ ] **Visibility state** сохраняется при навигации - -### 2.2 Visual Debug toggle -- [ ] **Home** включает/выключает visual debug -- [ ] **Toast notification** появляется: "Visual Debug Enabled/Disabled" - ---- - -## Критерии прохождения -- [ ] Навигация работает в обе стороны -- [ ] Toggle функции работают -- [ ] Данные обновляются в реальном времени diff --git a/Content/Debug/Structs/S_DebugPage.ts b/Content/Debug/Structs/S_DebugPage.ts index 8bca320..12fbe29 100644 --- a/Content/Debug/Structs/S_DebugPage.ts +++ b/Content/Debug/Structs/S_DebugPage.ts @@ -1,7 +1,7 @@ -// Debug/Structs/S_DebugPage.ts +// Content/Debug/Structs/S_DebugPage.ts -import type { Float } from '#root/UE/Float.ts'; -import type { Text } from '#root/UE/Text.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import type { Text } from '/Content/UE/Text.ts'; export interface S_DebugPage { PageID: string; diff --git a/Content/Debug/TDD.md b/Content/Debug/TDD.md deleted file mode 100644 index 5b12796..0000000 --- a/Content/Debug/TDD.md +++ /dev/null @@ -1,557 +0,0 @@ -[//]: # (Debug/TDD.md) - -# Система Debug - Техническая Документация - -## Обзор -Система динамической отладки для мониторинга параметров компонентов в реальном времени. Компоненты самостоятельно регистрируют свои debug страницы и управляют их обновлением без централизованной конфигурации. - -## Архитектурные принципы -- **Децентрализация:** Каждый компонент регистрирует и обновляет свои страницы независимо -- **Гибкость:** Страницы могут добавляться/удаляться в runtime без предварительной конфигурации -- **Производительность:** Индивидуальный контроль частоты обновления для каждой страницы -- **Простота:** Минимум кода для добавления debug информации в любой компонент - -## Компоненты системы - -### AC_DebugHUD (Core Component) -**Ответственности:** -- Регистрация debug страниц от любых компонентов -- Управление навигацией и отображением страниц -- Контроль видимости debug интерфейса -- Расчет FPS и управление обновлением UI - -**Ключевые публичные функции:** - -#### Управление страницами -- **`AddDebugPage(PageID, Title, RefreshRate, IsVisible)`** - - Регистрирует новую debug страницу или обновляет существующую - - `PageID`: Уникальный идентификатор (string) - - `Title`: Заголовок страницы (Text) - - `RefreshRate`: Частота обновления в Hz (number, default 30) - - `IsVisible`: Видимость страницы (boolean, default true) - -- **`UpdatePageContent(PageID, Content)`** - - Обновляет содержимое страницы - - Вызывается из Tick компонента владельца страницы - - `Content`: Текстовое содержимое (Text) - -- **`ShouldUpdatePage(PageID, CurrentTime)`** - - Проверяет, нужно ли обновлять страницу согласно RefreshRate - - Возвращает `true` если прошло достаточно времени - - Автоматически обновляет LastUpdateTime при возврате `true` - -- **`RemoveDebugPage(PageID)`** - - Удаляет страницу из системы - - Автоматически корректирует CurrentPageIndex - -- **`SetPageVisibility(PageID, IsVisible)`** - - Управляет видимостью страницы без удаления - -#### Навигация -- **`ToggleDebugHUD()`** - - Переключает видимость всего debug интерфейса - -- **`NextPage()` / `PreviousPage()`** - - Навигация между видимыми страницами с циклическим переходом - -- **`ToggleVisualDebug()`** - - Включение/выключение визуальной отладки (debug draw) - -#### Система -- **`InitializeDebugHUD(ToastComponent, InputDeviceComponent)`** - - Инициализация системы с опциональными компонентами - - Создание виджета и подготовка к регистрации страниц - -- **`UpdateHUD(CurrentTime)`** - - Основной цикл обновления UI - - Расчет FPS и обновление отображения - - Вызывается из Tick главного персонажа - -**Ключевые приватные функции:** - -#### Утилиты поиска -- **`FindPageIndex(PageID)`** - Поиск индекса страницы по ID -- **`GetVisiblePages()`** - Получение только видимых страниц -- **`GetCurrentPage()`** - Получение активной страницы - -#### Валидация -- **`IsCurrentPageValid(visiblePagesCount)`** - Проверка валидности индекса -- **`IsTimeToUpdate(timeSinceLastUpdate, updateInterval)`** - Проверка времени обновления -- **`IsAtFirstPage()`** - Проверка, является ли текущая страница первой - -#### Производительность -- **`ShouldUpdateFPS(currentTime)`** - Проверка необходимости пересчета FPS -- **`UpdateFPSCounter(currentTime)`** - Расчет FPS на основе кадров - -#### Виджет управление -- **`GetControlHints()`** - Получение подсказок управления по типу устройства -- **`UpdateWidgetDisplay()`** - Обновление содержимого виджета -- **`GetNavigationText()`** - Генерация текста навигации -- **`CreateDebugWidget()`** - Создание экземпляра виджета -- **`UpdateWidgetVisibility()`** - Обновление видимости виджета -- **`ShouldShowDebugHUD()`** - Проверка условий отображения HUD - -### WBP_DebugHUD (UI Widget) -**Ответственности:** -- Отображение debug информации в структурированном виде -- Управление тремя текстовыми секциями: заголовок, контент, навигация -- Автоматическое обновление при изменении данных - -**Ключевые функции:** -- `SetHeaderText()` - Установка заголовка текущей страницы -- `SetContentText()` - Обновление основного контента страницы -- `SetNavigationText()` - Отображение информации о навигации и FPS - -### S_DebugPage (Data Structure) -**Поля:** -```typescript -{ - PageID: string; // Уникальный идентификатор страницы - Title: Text; // Заголовок для отображения - Content: Text; // Текущее содержимое страницы - RefreshRate: Float; // Частота обновления (Hz) - IsVisible: boolean; // Флаг видимости - LastUpdateTime: Float; // Время последнего обновления -} -``` - -## Workflow использования - -### Регистрация debug страницы в компоненте - -```typescript -// Movement/Components/AC_Movement.ts - -export class AC_Movement extends ActorComponent { - private DebugHUDRef: AC_DebugHUD | null = null; - - public BeginPlay(): void { - super.BeginPlay(); - - // Получаем ссылку на DebugHUD - this.DebugHUDRef = this.GetOwner().FindComponentByClass(AC_DebugHUD); - - if (SystemLibrary.IsValid(this.DebugHUDRef)) { - // Регистрируем страницы движения - this.DebugHUDRef.AddDebugPage( - 'MovementBasics', // Уникальный ID - 'Movement Info', // Заголовок - 30, // 30 Hz - true // Видимая - ); - - this.DebugHUDRef.AddDebugPage( - 'MovementPhysics', - 'Physics Details', - 60 // 60 Hz для высокочастотных данных - ); - } - } - - public TickComponent(DeltaTime: Float): void { - super.TickComponent(DeltaTime); - - // Обновляем свою логику - this.UpdateMovement(DeltaTime); - - // Обновляем debug страницы - this.UpdateDebugPages(); - } - - private UpdateDebugPages(): void { - if (!SystemLibrary.IsValid(this.DebugHUDRef)) return; - - const currentTime = this.GetWorld().GetTimeSeconds(); - - // Проверяем нужно ли обновлять страницу (учитывает RefreshRate) - if (this.DebugHUDRef.ShouldUpdatePage('MovementBasics', currentTime)) { - const content = [ - `Speed: ${this.Speed.toFixed(2)} cm/s`, - `Acceleration: ${this.Acceleration.toFixed(2)} cm/s²`, - `Is Grounded: ${this.IsGrounded ? 'Yes' : 'No'}` - ].join('\n'); - - this.DebugHUDRef.UpdatePageContent('MovementBasics', content); - } - - if (this.DebugHUDRef.ShouldUpdatePage('MovementPhysics', currentTime)) { - const content = [ - `Velocity: ${this.GetVelocity().Size().toFixed(2)} cm/s`, - `Mass: ${this.GetMass().toFixed(2)} kg`, - `Friction: ${this.GetFriction().toFixed(3)}` - ].join('\n'); - - this.DebugHUDRef.UpdatePageContent('MovementPhysics', content); - } - } -} -``` - -### Инициализация в главном персонаже - -```typescript -// Characters/BP_MainCharacter.ts - -export class BP_MainCharacter extends Character { - public DebugHUDComponent: AC_DebugHUD; - - public BeginPlay(): void { - super.BeginPlay(); - - // Инициализация DebugHUD (должна быть ПЕРВОЙ) - this.DebugHUDComponent.InitializeDebugHUD( - this.ToastSystemComponent, - this.InputDeviceComponent - ); - - // После этого все компоненты могут регистрировать свои страницы - } - - public Tick(DeltaTime: Float): void { - super.Tick(DeltaTime); - - const currentTime = this.GetGameTimeSinceCreation(); - - // Обновляем только UI, не контент страниц - this.DebugHUDComponent.UpdateHUD(currentTime); - } -} -``` - -### Динамическое управление страницами - -```typescript -// Добавление страницы в runtime -public EnableAdvancedDebug(): void { - if (SystemLibrary.IsValid(this.DebugHUDRef)) { - this.DebugHUDRef.AddDebugPage( - 'AdvancedMetrics', - 'Advanced Metrics', - 120 // Очень высокая частота - ); - } -} - -// Удаление страницы -public DisableAdvancedDebug(): void { - if (SystemLibrary.IsValid(this.DebugHUDRef)) { - this.DebugHUDRef.RemoveDebugPage('AdvancedMetrics'); - } -} - -// Скрытие/показ страницы без удаления -public TogglePhysicsDebug(): void { - if (SystemLibrary.IsValid(this.DebugHUDRef)) { - this.showPhysics = !this.showPhysics; - this.DebugHUDRef.SetPageVisibility('MovementPhysics', this.showPhysics); - } -} -``` - -## Преимущества нового подхода - -### ✅ Децентрализация -- Каждый компонент управляет своими debug страницами -- Нет необходимости модифицировать централизованные DataTable или enum'ы -- Компонент владеет логикой генерации своего debug контента - -### ✅ Гибкость -- Страницы добавляются/удаляются динамически в runtime -- Легко менять выводимую информацию прямо в компоненте -- Условная регистрация страниц (например, только в Debug билдах) - -### ✅ Простота использования -```typescript -// Всего 3 шага: -// 1. Регистрация в BeginPlay -this.DebugHUD.AddDebugPage('MyPage', 'My Title', 30); - -// 2. Проверка в Tick -if (this.DebugHUD.ShouldUpdatePage('MyPage', currentTime)) { - // 3. Обновление контента - this.DebugHUD.UpdatePageContent('MyPage', this.GetDebugText()); -} -``` - -### ✅ Индивидуальный контроль производительности -- Каждая страница имеет свой RefreshRate -- Критичные данные: 60-120 Hz -- Обычные данные: 30 Hz -- Статичные данные: 15 Hz или меньше - -### ✅ Blueprint-совместимость -- Все параметры - простые типы (string, Text, number, boolean) -- Нет callback'ов или сложных структур данных -- Можно использовать как из C++/TypeScript, так и из Blueprint - -## Performance considerations - -### Оптимизации -- **Smart update timing:** `ShouldUpdatePage()` автоматически контролирует частоту -- **Single widget update:** UpdateHUD обновляет только текущую видимую страницу -- **Lazy evaluation:** Контент генерируется только когда страница видима и нужно обновление -- **FPS calculation:** Раз в секунду, не влияет на gameplay - -### Benchmarks -- **AddDebugPage:** <0.1ms (простое добавление в массив) -- **UpdatePageContent:** <0.05ms (обновление одного элемента массива) -- **ShouldUpdatePage:** <0.05ms (простая проверка времени) -- **UpdateHUD (widget refresh):** <0.2ms (обновление UI элементов) -- **Memory per page:** ~200 bytes (структура + strings) - -### Best Practices для производительности - -```typescript -// ✅ Хорошо - контролируемое обновление -if (this.DebugHUD.ShouldUpdatePage('MyPage', currentTime)) { - this.DebugHUD.UpdatePageContent('MyPage', this.BuildContent()); -} - -// ✅ Хорошо - разная частота для разных данных -this.DebugHUD.AddDebugPage('CriticalData', 'Critical', 60); // Частое -this.DebugHUD.AddDebugPage('GeneralInfo', 'General', 30); // Обычное -this.DebugHUD.AddDebugPage('StaticData', 'Static', 5); // Редкое - -// ❌ Плохо - обновление без проверки частоты -this.DebugHUD.UpdatePageContent('MyPage', content); // Каждый кадр! - -// ❌ Плохо - слишком высокая частота для некритичных данных -this.DebugHUD.AddDebugPage('SlowData', 'Slow', 120); // Избыточно -``` - -## Система тестирования - -### FT_DebugSystem (Basic Functionality) -**Покрывает:** -- Успешность инициализации системы (`IsInitialized = true`) -- Валидность компонента DebugHUD после инициализации -- Корректность создания виджета -- Базовую функциональность регистрации страниц - -**Тестовый сценарий:** -```typescript -1. Инициализация DebugHUD -2. Проверка IsInitialized == true -3. Проверка валидности компонента через SystemLibrary.IsValid() -``` - -### FT_DebugNavigation (Navigation System) -**Покрывает:** -- Корректность индексации при навигации -- Валидность CurrentPageIndex после NextPage/PreviousPage -- Циклическое поведение при достижении границ -- Устойчивость к многократным переходам - -**Тестовый сценарий:** -```typescript -1. Инициализация с проверкой начального состояния -2. NextPage() → проверка индекса в пределах [0, VisiblePages.length) -3. PreviousPage() → проверка индекса в пределах [0, VisiblePages.length) -4. Множественные переходы → индекс всегда валидный -``` - -**Валидация состояния:** -```typescript -private IsStateValid(): boolean { - const { VisiblePagesLength, CurrentPageIndex } = this.DebugHUD.GetTestData(); - return ( - VisiblePagesLength > 0 && - CurrentPageIndex >= 0 && - CurrentPageIndex < VisiblePagesLength - ); -} -``` - -### FT_DebugPageManagement (NEW - Page Operations) -**Покрывает:** -- Динамическое добавление страниц через AddDebugPage -- Обновление контента через UpdatePageContent -- Проверку частоты обновления через ShouldUpdatePage -- Удаление страниц через RemoveDebugPage -- Управление видимостью через SetPageVisibility - -**Тестовый сценарий:** -```typescript -1. AddDebugPage('TestPage1', 'Test', 30) - → Проверка что страница добавлена (DebugPages.length == 1) - -2. UpdatePageContent('TestPage1', 'New Content') - → Проверка что контент обновился - -3. ShouldUpdatePage('TestPage1', time) - → Проверка что возвращает true при первом вызове - → Проверка что возвращает false сразу после - -4. AddDebugPage('TestPage2', 'Test2', 60) - → Проверка что страница добавлена (DebugPages.length == 2) - -5. SetPageVisibility('TestPage2', false) - → Проверка что VisiblePages.length == 1 - -6. RemoveDebugPage('TestPage1') - → Проверка что страница удалена (DebugPages.length == 1) - → Проверка что CurrentPageIndex корректно обновился -``` - -## Структура файлов - -``` -Content/ -├── Debug/ -│ ├── Components/ -│ │ └── AC_DebugHUD.ts # Main debug system component -│ ├── Structs/ -│ │ └── S_DebugPage.ts # Page data structure -│ ├── UI/ -│ │ └── WBP_DebugHUD.ts # Debug HUD widget -│ └── Tests/ -│ ├── FT_DebugSystem.ts # Basic functionality tests -│ ├── FT_DebugNavigation.ts # Navigation system tests -│ └── FT_DebugPageManagement.ts # Page operations tests (NEW) -├── Input/ -│ └── IMC_Default.ts # Input mapping integration -└── Characters/ - └── BP_MainCharacter.ts # Main integration point -``` - -## Примеры использования из разных компонентов - -### Camera Component Debug -```typescript -export class AC_Camera extends ActorComponent { - private DebugHUDRef: AC_DebugHUD | null = null; - - public BeginPlay(): void { - super.BeginPlay(); - - this.DebugHUDRef = this.GetOwner().FindComponentByClass(AC_DebugHUD); - - if (SystemLibrary.IsValid(this.DebugHUDRef)) { - this.DebugHUDRef.AddDebugPage( - 'CameraInfo', - 'Camera State', - 30 - ); - } - } - - public TickComponent(DeltaTime: Float): void { - super.TickComponent(DeltaTime); - - if (!SystemLibrary.IsValid(this.DebugHUDRef)) return; - - const currentTime = this.GetWorld().GetTimeSeconds(); - - if (this.DebugHUDRef.ShouldUpdatePage('CameraInfo', currentTime)) { - const content = [ - `FOV: ${this.GetFOV().toFixed(1)}°`, - `Distance: ${this.GetCameraDistance().toFixed(2)} cm`, - `Pitch: ${this.GetPitch().toFixed(1)}°`, - `Yaw: ${this.GetYaw().toFixed(1)}°`, - `Target: ${this.GetTargetLocation().ToString()}` - ].join('\n'); - - this.DebugHUDRef.UpdatePageContent('CameraInfo', content); - } - } -} -``` - -### Network Component Debug -```typescript -export class AC_NetworkReplication extends ActorComponent { - private DebugHUDRef: AC_DebugHUD | null = null; - - public BeginPlay(): void { - super.BeginPlay(); - - this.DebugHUDRef = this.GetOwner().FindComponentByClass(AC_DebugHUD); - - if (SystemLibrary.IsValid(this.DebugHUDRef)) { - // Регистрируем только в сетевой игре - if (this.GetWorld().IsNetMode()) { - this.DebugHUDRef.AddDebugPage( - 'NetworkStats', - 'Network Statistics', - 15 // Обновляем реже для сетевых данных - ); - } - } - } - - public TickComponent(DeltaTime: Float): void { - super.TickComponent(DeltaTime); - - if (!SystemLibrary.IsValid(this.DebugHUDRef)) return; - - const currentTime = this.GetWorld().GetTimeSeconds(); - - if (this.DebugHUDRef.ShouldUpdatePage('NetworkStats', currentTime)) { - const content = [ - `Ping: ${this.GetPing()}ms`, - `Packet Loss: ${this.GetPacketLoss().toFixed(2)}%`, - `Bandwidth: ${this.GetBandwidth().toFixed(1)} KB/s`, - `Connected: ${this.IsConnected() ? 'Yes' : 'No'}`, - `Players: ${this.GetPlayerCount()}` - ].join('\n'); - - this.DebugHUDRef.UpdatePageContent('NetworkStats', content); - } - } -} -``` - -### Conditional Debug Pages -```typescript -export class AC_AdvancedSystem extends ActorComponent { - private DebugHUDRef: AC_DebugHUD | null = null; - private showDetailedDebug: boolean = false; - - public BeginPlay(): void { - super.BeginPlay(); - - this.DebugHUDRef = this.GetOwner().FindComponentByClass(AC_DebugHUD); - - if (SystemLibrary.IsValid(this.DebugHUDRef)) { - // Базовая страница всегда - this.DebugHUDRef.AddDebugPage('BasicInfo', 'Basic Info', 30); - - // Детальная только в Debug билде - if (BUILD_DEBUG) { - this.DebugHUDRef.AddDebugPage( - 'DetailedInfo', - 'Detailed Debug', - 120, - false // Скрыта по умолчанию - ); - } - } - } - - public ToggleDetailedDebug(): void { - this.showDetailedDebug = !this.showDetailedDebug; - - if (SystemLibrary.IsValid(this.DebugHUDRef)) { - this.DebugHUDRef.SetPageVisibility('DetailedInfo', this.showDetailedDebug); - } - } -} -``` - -## Известные ограничения - -### Текущие ограничения -1. **Текстовый контент только** - Нет поддержки графиков, диаграмм, интерактивных элементов -2. **Фиксированный layout** - Трехсекционный layout (header, content, navigation) не настраивается -3. **Линейная навигация** - Только последовательный переход между страницами -4. **Глобальный FPS** - Один FPS counter для всей системы - -### Архитектурные решения -1. **Компоненты управляют своими страницами** - Каждый компонент отвечает за регистрацию и обновление -2. **String-based PageID** - Простота использования в ущерб типобезопасности -3. **Tick-based updates** - Компоненты обновляют страницы в своем Tick -4. **No data caching** - Контент генерируется при каждом обновлении - -## Миграция со старого подхода - -### Было (DT_DebugPages + E_DebugUpdateFunction) diff --git a/Content/Debug/Tests/FT_DebugNavigation.ts b/Content/Debug/Tests/FT_DebugNavigation.ts deleted file mode 100644 index 38d186d..0000000 --- a/Content/Debug/Tests/FT_DebugNavigation.ts +++ /dev/null @@ -1,107 +0,0 @@ -// Debug/Tests/FT_DebugNavigation.ts - -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import type { Integer } from '#root/UE/Integer.ts'; - -/** - * Functional Test: Debug HUD Navigation System - * Tests page navigation state management during NextPage/PreviousPage operations - */ -export class FT_DebugNavigation extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test entry point - validates navigation state during page operations - * Uses nested validation to ensure CurrentPageIndex stays valid - */ - EventStartTest(): void { - this.DebugHUDComponent.InitializeDebugHUD( - this.ToastSystemComponent, - this.InputDeviceComponent - ); - - this.IfValid('Debug HUD: Navigation invalid initial state', () => { - this.IfValid( - 'Debug HUD: NextPage failed — Invalid state before NextPage', - () => { - this.DebugHUDComponent.NextPage(); - - this.IfValid( - 'Debug HUD: NextPage failed — State became invalid after NextPage', - () => { - this.DebugHUDComponent.PreviousPage(); - - this.IfValid( - 'Debug HUD: PrevPage failed — State became invalid after PreviousPage', - () => { - this.FinishTest(EFunctionalTestResult.Succeeded); - } - ); - } - ); - } - ); - }); - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // MACROS - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Validates current page index and executes callback if state is valid - * @param Message - Error message if validation fails - * @param Out - Callback to execute if state is valid - */ - private IfValid(Message: string, Out: () => void): void { - const IsPageIndexOutOfBounds = ( - visiblePagesLength: Integer, - currentPage: Integer - ): boolean => visiblePagesLength > 0 && currentPage >= visiblePagesLength; - - if ( - !IsPageIndexOutOfBounds( - this.DebugHUDComponent.GetTestData().VisiblePagesLength, - this.DebugHUDComponent.GetTestData().CurrentPageIndex - ) && - this.DebugHUDComponent.GetTestData().CurrentPageIndex >= 0 - ) { - Out(); - } else { - this.FinishTest(EFunctionalTestResult.Failed, Message); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Debug HUD system - primary component under test - * Tests page navigation state management - * @category Components - */ - DebugHUDComponent = new AC_DebugHUD(); - - /** - * Toast notification system - required for debug HUD initialization - * @category Components - */ - ToastSystemComponent = new AC_ToastSystem(); - - /** - * Input device detection system - used for input device debug page testing - * @category Components - */ - InputDeviceComponent = new AC_InputDevice(); -} diff --git a/Content/Debug/Tests/FT_DebugNavigation.uasset b/Content/Debug/Tests/FT_DebugNavigation.uasset deleted file mode 100644 index 4f02351..0000000 --- a/Content/Debug/Tests/FT_DebugNavigation.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d316fe79108783bdd9890de8afc69aa1f9a130d789da6bb5128d0cfe3c2457ad -size 94265 diff --git a/Content/Debug/Tests/FT_DebugPageManagement.ts b/Content/Debug/Tests/FT_DebugPageManagement.ts deleted file mode 100644 index 8802bb7..0000000 --- a/Content/Debug/Tests/FT_DebugPageManagement.ts +++ /dev/null @@ -1,261 +0,0 @@ -// Debug/Tests/FT_DebugPageManagement.ts - -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; - -/** - * Functional Test: Debug Page Management - * Tests dynamic page registration, content updates, and lifecycle operations - */ -export class FT_DebugPageManagement extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test entry point - validates all page management operations - * Tests: Add, Update, ShouldUpdate, Visibility, Remove - */ - EventStartTest(): void { - this.DebugHUDComponent.InitializeDebugHUD( - this.ToastSystemComponent, - this.InputDeviceComponent - ); - - this.DebugHUDComponent.AddDebugPage('TestPage1', 'Test Page 1', 30, true); - - const pageCount = this.DebugHUDComponent.GetTestData().DebugPages.length; - - if (pageCount === 1) { - const testContent = 'Test Content 123'; - - this.DebugHUDComponent.UpdatePageContent('TestPage1', testContent); - - const page = this.DebugHUDComponent.GetTestData().DebugPages.Get(0); - const contentMatches = page.Content === testContent; - - if (contentMatches) { - const currentTime = SystemLibrary.GetGameTimeInSeconds(); - - // First call should return true (no previous update) - const firstCall = this.DebugHUDComponent.ShouldUpdatePage( - 'TestPage1', - currentTime - ); - - // Immediate second call should return false (just updated) - const secondCall = this.DebugHUDComponent.ShouldUpdatePage( - 'TestPage1', - currentTime - ); - - if (firstCall && !secondCall) { - this.DebugHUDComponent.AddDebugPage( - 'TestPage2', - 'Test Page 2', - 60, - true - ); - - const pageCount = - this.DebugHUDComponent.GetTestData().DebugPages.length; - const visibleCount = - this.DebugHUDComponent.GetTestData().VisiblePagesLength; - - if (pageCount === 2 && visibleCount === 2) { - // Hide second page - this.DebugHUDComponent.SetPageVisibility('TestPage2', false); - - const totalCount = - this.DebugHUDComponent.GetTestData().DebugPages.length; - const visibleCount = - this.DebugHUDComponent.GetTestData().VisiblePagesLength; - - if (totalCount === 2 && visibleCount === 1) { - // Remove first page - this.DebugHUDComponent.RemoveDebugPage('TestPage1'); - - const totalCount = - this.DebugHUDComponent.GetTestData().DebugPages.length; - const currentIndex = - this.DebugHUDComponent.GetTestData().CurrentPageIndex; - - if (totalCount === 1 && currentIndex === 0) { - // Re-add page with same ID but different settings - this.DebugHUDComponent.AddDebugPage( - 'TestPage2', - 'Updated Title', - 120, - true - ); - - const totalCount = - this.DebugHUDComponent.GetTestData().DebugPages.length; - const visibleCount = - this.DebugHUDComponent.GetTestData().VisiblePagesLength; - const page = - this.DebugHUDComponent.GetTestData().DebugPages.Get(0); - const titleMatches = page.Title === 'Updated Title'; - const refreshRateMatches = page.RefreshRate === 120; - - if ( - totalCount === 1 && - visibleCount === 1 && - titleMatches && - refreshRateMatches - ) { - // Add pages with different refresh rates - this.DebugHUDComponent.AddDebugPage( - 'FastPage', - 'Fast Page', - 120, - true - ); - - this.DebugHUDComponent.AddDebugPage( - 'SlowPage', - 'Slow Page', - 10, - true - ); - - const currentTime = SystemLibrary.GetGameTimeInSeconds(); - - // Both should update on first call - const fastShouldUpdate = - this.DebugHUDComponent.ShouldUpdatePage( - 'FastPage', - currentTime - ); - const slowShouldUpdate = - this.DebugHUDComponent.ShouldUpdatePage( - 'SlowPage', - currentTime - ); - - // Wait for fast page interval (1/120 = 0.0083s) but not slow (1/10 = 0.1s) - const fastUpdateTime = currentTime + 0.01; - - const fastShouldUpdateAgain = - this.DebugHUDComponent.ShouldUpdatePage( - 'FastPage', - fastUpdateTime - ); - const slowShouldNotUpdate = - this.DebugHUDComponent.ShouldUpdatePage( - 'SlowPage', - fastUpdateTime - ); - - if ( - fastShouldUpdate && - slowShouldUpdate && - fastShouldUpdateAgain && - !slowShouldNotUpdate - ) { - // Try to update non-existent page (should not crash) - this.DebugHUDComponent.UpdatePageContent( - 'NonExistentPage', - 'Test' - ); - - // Try to remove non-existent page (should not crash) - this.DebugHUDComponent.RemoveDebugPage('NonExistentPage'); - - // Try to check non-existent page (should return false) - const currentTime = SystemLibrary.GetGameTimeInSeconds(); - const shouldUpdate = - this.DebugHUDComponent.ShouldUpdatePage( - 'NonExistentPage', - currentTime - ); - - if (!shouldUpdate) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Test 9 Failed: ShouldUpdatePage returned true for non-existent page' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Test 8 Failed: Refresh rates incorrect (fast1: ${fastShouldUpdate}, slow1: ${slowShouldUpdate}, fast2: ${fastShouldUpdateAgain}, slow2: ${slowShouldNotUpdate})` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Test 7 Failed: Update registration incorrect (count: ${totalCount}, title: ${titleMatches}, rate: ${refreshRateMatches})` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Test 6 Failed: Remove incorrect (count: ${totalCount}, index: ${currentIndex})` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Test 5 Failed: Visibility incorrect (total: ${totalCount}, visible: ${visibleCount})` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Test 4 Failed: Expected 2 pages (total: ${pageCount}, visible: ${visibleCount})` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Test 3 Failed: ShouldUpdatePage timing incorrect (first: ${firstCall}, second: ${secondCall})` - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Test 2 Failed: Content did not update correctly' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Test 1 Failed: Expected 1 page, got ${pageCount}` - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Debug HUD system - primary component under test - * Tests all page management operations - * @category Components - */ - DebugHUDComponent = new AC_DebugHUD(); - - /** - * Toast notification system - required for debug HUD initialization - * @category Components - */ - ToastSystemComponent = new AC_ToastSystem(); - - /** - * Input device detection system - required for debug HUD initialization - * @category Components - */ - InputDeviceComponent = new AC_InputDevice(); -} diff --git a/Content/Debug/Tests/FT_DebugPageManagement.uasset b/Content/Debug/Tests/FT_DebugPageManagement.uasset deleted file mode 100644 index 7f221d0..0000000 --- a/Content/Debug/Tests/FT_DebugPageManagement.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ce33f7f74151311814fc56699cda0dae010b72b3c670b2a87b758ab9c4d412c -size 429493 diff --git a/Content/Debug/Tests/FT_DebugSystem.ts b/Content/Debug/Tests/FT_DebugSystem.ts deleted file mode 100644 index 1a78b1a..0000000 --- a/Content/Debug/Tests/FT_DebugSystem.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Debug/Tests/FT_DebugSystem.ts - -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; - -/** - * Functional Test: Debug System Basic Functionality - * Validates initialization, component validity, and data table consistency - */ -export class FT_DebugSystem extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test entry point - validates basic debug system functionality - * Uses nested validation to check initialization, page count, and component validity - */ - EventStartTest(): void { - this.DebugHUDComponent.InitializeDebugHUD( - this.ToastSystemComponent, - this.InputDeviceComponent - ); - - if (this.DebugHUDComponent.GetTestData().IsInitialized) { - if (SystemLibrary.IsValid(this.DebugHUDComponent)) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'DebugHUD component not valid' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Debug HUD failed to initialize' - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Debug HUD system - primary component under test - * Tests basic system initialization and component validity - * @category Components - */ - DebugHUDComponent = new AC_DebugHUD(); - - /** - * Toast notification system - required for debug HUD initialization - * @category Components - */ - ToastSystemComponent = new AC_ToastSystem(); - - /** - * Input device detection system - used for input device debug page testing - * @category Components - */ - InputDeviceComponent = new AC_InputDevice(); -} diff --git a/Content/Debug/Tests/FT_DebugSystem.uasset b/Content/Debug/Tests/FT_DebugSystem.uasset deleted file mode 100644 index 857e82a..0000000 --- a/Content/Debug/Tests/FT_DebugSystem.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f46264be9fe577a67eb4be517be47cc9321ae31c3ee57b4e05dfa2519bb93f85 -size 58283 diff --git a/Content/Debug/UI/WBP_DebugHUD.ts b/Content/Debug/UI/WBP_DebugHUD.ts index 65ff11c..1a42ea5 100644 --- a/Content/Debug/UI/WBP_DebugHUD.ts +++ b/Content/Debug/UI/WBP_DebugHUD.ts @@ -1,10 +1,9 @@ -// Debug/UI/WBP_DebugHUD.ts +// Content/Debug/UI/WBP_DebugHUD.ts -import type { AC_Movement } from '#root/Movement/AC_Movement.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import type { Text } from '#root/UE/Text.ts'; -import { TextBlock } from '#root/UE/TextBlock.ts'; -import { UserWidget } from '#root/UE/UserWidget.ts'; +import { SystemLibrary } from '/Content/UE/SystemLibrary.ts'; +import type { Text } from '/Content/UE/Text.ts'; +import { TextBlock } from '/Content/UE/TextBlock.ts'; +import { UserWidget } from '/Content/UE/UserWidget.ts'; /** * Debug HUD Widget for displaying system information @@ -107,13 +106,6 @@ export class WBP_DebugHUD extends UserWidget { // VARIABLES // ════════════════════════════════════════════════════════════════════════════════════════ - /** - * Reference to movement component for accessing debug data - * Set by AC_DebugHUD during initialization - * @category Components - */ - public MovementComponent: AC_Movement | null = null; - /** * Current page title text * Updated by AC_DebugHUD when switching pages diff --git a/Content/Debug/UI/WBP_DebugHUD.uasset b/Content/Debug/UI/WBP_DebugHUD.uasset index b2d92e2..4a93f99 100644 --- a/Content/Debug/UI/WBP_DebugHUD.uasset +++ b/Content/Debug/UI/WBP_DebugHUD.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c07b4ceb64ffcd3c384108edbef20d31d966d30e4ea3dddf9817db75e3d46e8 -size 103783 +oid sha256:d78f9cac51e19251bfad2a5856ebda1f5ec518f733c374fd2a339edbda3bdd23 +size 107557 diff --git a/Content/Input/Actions/IA_LeftTrigger.ts b/Content/Input/Actions/IA_LeftTrigger.ts index bcbb59f..6c8ecc5 100644 --- a/Content/Input/Actions/IA_LeftTrigger.ts +++ b/Content/Input/Actions/IA_LeftTrigger.ts @@ -1,6 +1,6 @@ -// Input/Actions/IA_LeftTrigger.ts +// Content/Input/Actions/IA_LeftTrigger.ts -import { InputAction } from '#root/UE/InputAction.ts'; -import { Name } from '#root/UE/Name.ts'; +import { InputAction } from '/Content/UE/InputAction.ts'; +import { Name } from '/Content/UE/Name.ts'; export const IA_LeftTrigger = new InputAction(null, new Name('IA_LeftTrigger')); diff --git a/Content/Input/Actions/IA_Look.ts b/Content/Input/Actions/IA_Look.ts index b480846..bd1c789 100644 --- a/Content/Input/Actions/IA_Look.ts +++ b/Content/Input/Actions/IA_Look.ts @@ -1,6 +1,6 @@ -// Input/Actions/IA_Look.ts +// Content/Input/Actions/IA_Look.ts -import { InputAction } from '#root/UE/InputAction.ts'; -import { Name } from '#root/UE/Name.ts'; +import { InputAction } from '/Content/UE/InputAction.ts'; +import { Name } from '/Content/UE/Name.ts'; export const IA_Look = new InputAction(null, new Name('IA_Look')); diff --git a/Content/Input/Actions/IA_Move.ts b/Content/Input/Actions/IA_Move.ts index b0462ae..24363e7 100644 --- a/Content/Input/Actions/IA_Move.ts +++ b/Content/Input/Actions/IA_Move.ts @@ -1,6 +1,6 @@ -// Input/Actions/IA_Move.ts +// Content/Input/Actions/IA_Move.ts -import { InputAction } from '#root/UE/InputAction.ts'; -import { Name } from '#root/UE/Name.ts'; +import { InputAction } from '/Content/UE/InputAction.ts'; +import { Name } from '/Content/UE/Name.ts'; export const IA_Move = new InputAction(null, new Name('IA_Move')); diff --git a/Content/Input/Actions/IA_NextDebugMode.ts b/Content/Input/Actions/IA_NextDebugMode.ts index e6864d7..43e01fa 100644 --- a/Content/Input/Actions/IA_NextDebugMode.ts +++ b/Content/Input/Actions/IA_NextDebugMode.ts @@ -1,5 +1,5 @@ -// Input/Actions/IA_NextDebugMode.ts +// Content/Input/Actions/IA_NextDebugMode.ts -import { InputAction } from '#root/UE/InputAction.ts'; +import { InputAction } from '/Content/UE/InputAction.ts'; export const IA_NextDebugMode = new InputAction(null, 'IA_NextDebugMode'); diff --git a/Content/Input/Actions/IA_PrevDebugMode.ts b/Content/Input/Actions/IA_PrevDebugMode.ts index afef139..c8eea64 100644 --- a/Content/Input/Actions/IA_PrevDebugMode.ts +++ b/Content/Input/Actions/IA_PrevDebugMode.ts @@ -1,5 +1,5 @@ -// Input/Actions/IA_PrevDebugMode.ts +// Content/Input/Actions/IA_PrevDebugMode.ts -import { InputAction } from '#root/UE/InputAction.ts'; +import { InputAction } from '/Content/UE/InputAction.ts'; export const IA_PrevDebugMode = new InputAction(null, 'IA_PrevDebugMode'); diff --git a/Content/Input/Actions/IA_RightTrigger.ts b/Content/Input/Actions/IA_RightTrigger.ts index 767ad84..13a5c66 100644 --- a/Content/Input/Actions/IA_RightTrigger.ts +++ b/Content/Input/Actions/IA_RightTrigger.ts @@ -1,5 +1,5 @@ -// Input/Actions/IA_RightTrigger.ts +// Content/Input/Actions/IA_RightTrigger.ts -import { InputAction } from '#root/UE/InputAction.ts'; +import { InputAction } from '/Content/UE/InputAction.ts'; export const IA_RightTrigger = new InputAction(null, 'IA_RightTrigger'); diff --git a/Content/Input/Actions/IA_ToggleHUD.ts b/Content/Input/Actions/IA_ToggleHUD.ts index 378c9b9..272e566 100644 --- a/Content/Input/Actions/IA_ToggleHUD.ts +++ b/Content/Input/Actions/IA_ToggleHUD.ts @@ -1,5 +1,5 @@ -// Input/Actions/IA_ToggleHUD.ts +// Content/Input/Actions/IA_ToggleHUD.ts -import { InputAction } from '#root/UE/InputAction.ts'; +import { InputAction } from '/Content/UE/InputAction.ts'; export const IA_ToggleHUD = new InputAction(null, 'IA_ToggleHUD'); diff --git a/Content/Input/Actions/IA_ToggleVisualDebug.ts b/Content/Input/Actions/IA_ToggleVisualDebug.ts index ab1e377..36a0b99 100644 --- a/Content/Input/Actions/IA_ToggleVisualDebug.ts +++ b/Content/Input/Actions/IA_ToggleVisualDebug.ts @@ -1,6 +1,6 @@ -// Input/Actions/IA_ToggleVisualDebug.ts +// Content/Input/Actions/IA_ToggleVisualDebug.ts -import { InputAction } from '#root/UE/InputAction.ts'; +import { InputAction } from '/Content/UE/InputAction.ts'; export const IA_ToggleVisualDebug = new InputAction( null, diff --git a/Content/Input/Components/AC_InputDevice.ts b/Content/Input/Components/AC_InputDevice.ts index 8aed077..fec3033 100644 --- a/Content/Input/Components/AC_InputDevice.ts +++ b/Content/Input/Components/AC_InputDevice.ts @@ -1,14 +1,14 @@ -// Input/Components/AC_InputDevice.ts +// Content/Input/Components/AC_InputDevice.ts -import type { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { ActorComponent } from '#root/UE/ActorComponent.ts'; -import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts'; -import type { Float } from '#root/UE/Float.ts'; -import { InputDeviceSubsystem } from '#root/UE/InputDeviceSubsystem.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; +import type { AC_DebugHUD } from '/Content/Debug/Components/AC_DebugHUD.ts'; +import type { AC_ToastSystem } from '/Content/Toasts/Components/AC_ToastSystem.ts'; +import { ActorComponent } from '/Content/UE/ActorComponent.ts'; +import { EHardwareDevicePrimaryType } from '/Content/UE/EHardwareDevicePrimaryType.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import { InputDeviceSubsystem } from '/Content/UE/InputDeviceSubsystem.ts'; +import type { Integer } from '/Content/UE/Integer.ts'; +import { SystemLibrary } from '/Content/UE/SystemLibrary.ts'; +import { E_MessageType } from '/Content/UI/Enums/E_MessageType.ts'; /** * Input Device Detection Component diff --git a/Content/Input/IMC_Default.ts b/Content/Input/IMC_Default.ts index 0bb64d4..fc29817 100644 --- a/Content/Input/IMC_Default.ts +++ b/Content/Input/IMC_Default.ts @@ -1,15 +1,15 @@ -// Input/IMC_Default.ts +// Content/Input/IMC_Default.ts -import { IA_LeftTrigger } from '#root/Input/Actions/IA_LeftTrigger.ts'; -import { IA_Look } from '#root/Input/Actions/IA_Look.ts'; -import { IA_Move } from '#root/Input/Actions/IA_Move.ts'; -import { IA_NextDebugMode } from '#root/Input/Actions/IA_NextDebugMode.ts'; -import { IA_PrevDebugMode } from '#root/Input/Actions/IA_PrevDebugMode.ts'; -import { IA_RightTrigger } from '#root/Input/Actions/IA_RightTrigger.ts'; -import { IA_ToggleHUD } from '#root/Input/Actions/IA_ToggleHUD.ts'; -import { IA_ToggleVisualDebug } from '#root/Input/Actions/IA_ToggleVisualDebug.ts'; -import { InputMappingContext } from '#root/UE/InputMappingContext.ts'; -import { Key } from '#root/UE/Key.ts'; +import { IA_LeftTrigger } from '/Content/Input/Actions/IA_LeftTrigger.ts'; +import { IA_Look } from '/Content/Input/Actions/IA_Look.ts'; +import { IA_Move } from '/Content/Input/Actions/IA_Move.ts'; +import { IA_NextDebugMode } from '/Content/Input/Actions/IA_NextDebugMode.ts'; +import { IA_PrevDebugMode } from '/Content/Input/Actions/IA_PrevDebugMode.ts'; +import { IA_RightTrigger } from '/Content/Input/Actions/IA_RightTrigger.ts'; +import { IA_ToggleHUD } from '/Content/Input/Actions/IA_ToggleHUD.ts'; +import { IA_ToggleVisualDebug } from '/Content/Input/Actions/IA_ToggleVisualDebug.ts'; +import { InputMappingContext } from '/Content/UE/InputMappingContext.ts'; +import { Key } from '/Content/UE/Key.ts'; export const IMC_Default = new InputMappingContext(); diff --git a/Content/Input/ManualTestingChecklist.md b/Content/Input/ManualTestingChecklist.md deleted file mode 100644 index c34d109..0000000 --- a/Content/Input/ManualTestingChecklist.md +++ /dev/null @@ -1,75 +0,0 @@ -[//]: # (Input/ManualTestingChecklist.md) - -# Input Device System - Manual Testing Checklist - -## Тестовая среда -- **Персонаж:** BP_MainCharacter с ShowDebugInfo = true -- **Клавиши:** PageUp/PageDown для навигации в Debug HUD -- **Требования:** InputDeviceComponent инициализирован - ---- - -## 1. Debug HUD Integration - -### 1.1 Input Device Info Page -- [ ] **Page 4** отображается как "Input Device Detection" -- [ ] **PageUp/PageDown** позволяет перейти на Input Device page -- [ ] **Содержимое страницы** показывает: - - Primary Type: [тип устройства UE] - - Is Initialized: [true/false] - -### 1.2 Real-time Device Detection -- [ ] **При использовании мыши/клавиатуры** Primary Type показывает "Keyboard & Mouse" -- [ ] **При подключении геймпада** Primary Type автоматически меняется на "Gamepad" - ---- - -## 2. Автоматическая детекция устройств - -### 2.1 Keyboard & Mouse Detection -- [ ] **Движение мыши** автоматически переключает на Keyboard & Mouse -- [ ] **Нажатие клавиш** (WASD, пробел, etc.) переключает на Keyboard & Mouse -- [ ] **Primary Type** показывает "KeyboardAndMouse" - -### 2.2 Gamepad Detection -- [ ] **Движение стиков** автоматически переключает на Gamepad -- [ ] **Нажатие кнопок геймпада** переключает на Gamepad -- [ ] **Primary Type** показывает "Gamepad" - ---- - -## 3. API Functions Testing - -### 3.1 Device Type Queries (Binary) -- [ ] **IsKeyboard()** возвращает true для всех устройств кроме Gamepad -- [ ] **IsGamepad()** возвращает true только для геймпадов -- [ ] **IsKeyboard() и IsGamepad()** никогда не возвращают одинаковые значения -- [ ] **GetCurrentInputDevice()** возвращает корректный EHardwareDevicePrimaryType - ---- - -## 4. Error Handling - -### 4.1 Edge Cases -- [ ] **Отключение устройств** обрабатывается корректно -- [ ] **Подключение новых устройств** детектируется автоматически -- [ ] **System console** не содержит ошибок input detection -- [ ] **Performance** остается стабильной при активном использовании - -### 4.2 Integration Stability -- [ ] **Debug HUD** стабильно работает с device detection -- [ ] **Частые переключения** устройств не вызывают проблем -- [ ] **AC_InputDevice** корректно инициализируется -- [ ] **IsGamepad/IsKeyboard** всегда возвращают корректные значения - ---- - -## Критерии прохождения -- [ ] All device types correctly detected and displayed -- [ ] Real-time switching works seamlessly through UE subsystem -- [ ] Debug HUD shows complete hardware information -- [ ] No console errors during normal operation -- [ ] API functions return consistent results -- [ ] Native UE InputDeviceSubsystem integration works properly - -**Примечание:** Система использует только встроенную InputDeviceSubsystem от Unreal Engine. Никаких симуляций или искусственных переключений. diff --git a/Content/Input/TDD.md b/Content/Input/TDD.md deleted file mode 100644 index 8cd5884..0000000 --- a/Content/Input/TDD.md +++ /dev/null @@ -1,411 +0,0 @@ -[//]: # (Input/TDD.md) - -# Input Device Detection System - Техническая Документация - -## Обзор -Event-driven система определения типа устройства ввода, основанная на делегате OnInputHardwareDeviceChanged от Unreal Engine 5.3+. Предоставляет простую бинарную классификацию устройств с automatic debouncing и минимальным overhead при отсутствии смены устройства. - -## Архитектурные принципы -- **Event-Driven Detection:** Использование OnInputHardwareDeviceChanged delegate вместо polling -- **Binary Simplicity:** Только два состояния - геймпад или клавиатура/мышь -- **Automatic Debouncing:** Встроенная защита от rapid device switching -- **Zero Polling Overhead:** Реакция только на реальные события смены устройства - -## Единственный компонент - -### AC_InputDevice (Event-Driven Wrapper) -**Ответственности:** -- Event-driven обертка над Unreal Engine InputDeviceSubsystem -- Automatic debouncing для предотвращения flickering -- Бинарная классификация: IsGamepad() vs IsKeyboard() -- Интеграция с Toast notification system для debug - -**Ключевые функции:** -- `InitializeDeviceDetection()` - регистрация delegate и initial detection -- `IsKeyboard()` / `IsGamepad()` - binary device queries -- `GetCurrentInputDevice()` - доступ к cached device state -- `OnInputHardwareDeviceChanged()` - event handler для device switching - -**Event-driven архитектура:** -```typescript -InputDeviceSubsystem.OnInputHardwareDeviceChanged.BindEvent() - → OnInputHardwareDeviceChanged() - → ProcessDeviceChange() - → Update cached state + Toast notification -``` - -## Система событий и debouncing - -### Event Registration -```typescript -// Регистрация event handler при инициализации -InputDeviceSubsystem.OnInputHardwareDeviceChanged.BindEvent( - this.OnInputHardwareDeviceChanged.bind(this) -); -``` - -### Event Processing Flow -```typescript -OnInputHardwareDeviceChanged(UserId, DeviceId) → - GetInputDeviceHardwareIdentifier(DeviceId) → - ProcessDeviceChange(PrimaryDeviceType) → - CanProcessDeviceChange() (debouncing check) → - Update CurrentDevice + LastChangeTime → - Toast notification -``` - -### Automatic Debouncing -```typescript -// Защита от rapid switching -private CanProcessDeviceChange(): boolean { - const HasCooldownExpired = (): boolean => - SystemLibrary.GetGameTimeInSeconds() - this.LastDeviceChangeTime >= - this.DeviceChangeCooldown; // 300ms по умолчанию - - return HasCooldownExpired(); -} -``` - -## Классификация устройств - -### Binary Device Logic -```typescript -// Вся логика классификации: -IsGamepad() → CurrentDevice === EHardwareDevicePrimaryType.Gamepad -IsKeyboard() → CurrentDevice === EHardwareDevicePrimaryType.KeyboardAndMouse -``` - -### Device Detection через Hardware Names -```typescript -// Определение типа устройства по событию: -OnInputHardwareDeviceChanged(UserId, DeviceId) → - InputDeviceSubsystem.GetInputDeviceHardwareIdentifier(DeviceId) → - .PrimaryDeviceType → - Update CurrentDevice state -``` - -### Mapping UE типов -```typescript -// Поддерживаемые устройства: -EHardwareDevicePrimaryType.Gamepad → IsGamepad() = true -EHardwareDevicePrimaryType.KeyboardAndMouse → IsKeyboard() = true -EHardwareDevicePrimaryType.Unspecified → fallback to previous state -``` - -## Производительность - -### Event-Driven преимущества -- **Zero polling overhead:** Обновления только при реальных событиях -- **Instant response:** Мгновенная реакция на device switching -- **Minimal CPU usage:** Нет постоянных проверок в Tick -- **Automatic state management:** UE Engine управляет device state - -### Benchmarks -- **Инициализация:** <0.1ms (регистрация delegate + initial detection) -- **Event processing:** <0.05ms на событие (с debouncing) -- **IsKeyboard/IsGamepad:** <0.001ms (cached state access) -- **Memory footprint:** ~50 bytes (cached state + timers) - -### Performance considerations -- **Event frequency:** Обычно 0-5 событий в секунду при активном switching -- **Debouncing cost:** Одно сравнение float времени на событие -- **No allocations:** Все операции работают с existing objects -- **Toast overhead:** Optional debug notifications не влияют на core performance - -## Debouncing система - -### Cooldown механизм -```typescript -private DeviceChangeCooldown: Float = 0.3; // 300ms стандартный интервал -private LastDeviceChangeTime: Float = 0; // Timestamp последней смены - -// Проверка при каждом событии: -if (SystemLibrary.GetGameTimeInSeconds() - LastDeviceChangeTime >= DeviceChangeCooldown) { - // Process device change -} else { - // Ignore rapid switching -} -``` - -### Защита от stick drift -Event-driven подход естественно защищает от большинства stick drift проблем: -- **Hardware events** срабатывают реже чем input polling -- **Debouncing** отфильтровывает rapid oscillation -- **Real device changes** (кнопки, отключение) проходят через систему - -## Интеграция с системами - -### С Toast System -```typescript -// Debug notifications при смене устройства -if (SystemLibrary.IsValid(this.ToastComponent)) { - this.ToastComponent.ShowToast( - `Input switched to ${NewDevice}`, - E_MessageType.Info - ); -} -``` - -### С Debug HUD System -```typescript -// Новая debug page для input device info: -UpdateInputDevicePage(): string { - const deviceType = this.InputDeviceComponent.IsGamepad() ? 'Gamepad' : 'Keyboard & Mouse'; - const isInitialized = this.InputDeviceComponent.IsInitialized ? 'Yes' : 'No'; - - return `Current Device: ${deviceType}\n` + - `Initialized: ${isInitialized}\n` + - `Last Change: ${this.GetTimeSinceLastChange()}s ago`; -} -``` - -### С Enhanced Input System (будущая интеграция) -```typescript -// Этап 6+: Input Mapping Context switching -OnDeviceChanged() → Branch: IsGamepad()? - True → Remove IMC_Keyboard + Add IMC_Gamepad - False → Remove IMC_Gamepad + Add IMC_Keyboard -``` - -## API Reference - -### Основные методы - -#### InitializeDeviceDetection() -```typescript -InitializeDeviceDetection(ToastComponentRef: AC_ToastSystem): void -``` -**Описание:** Инициализация event-driven device detection -**Параметры:** ToastComponentRef для debug notifications -**Когда вызывать:** EventBeginPlay в main character -**Эффекты:** Регистрирует delegate, выполняет initial detection, показывает success toast - -#### IsKeyboard() -```typescript -IsKeyboard(): boolean -``` -**Описание:** Проверка на клавиатуру/мышь (cached state) -**Возвращает:** True для KeyboardAndMouse устройств -**Performance:** <0.001ms (direct boolean comparison) -**Use case:** UI hints, input prompts - -#### IsGamepad() -```typescript -IsGamepad(): boolean -``` -**Описание:** Проверка на геймпад/контроллер (cached state) -**Возвращает:** True для Gamepad устройств -**Performance:** <0.001ms (direct enum comparison) -**Use case:** UI hints, control schemes - -#### GetCurrentInputDevice() -```typescript -GetCurrentInputDevice(): EHardwareDevicePrimaryType -``` -**Описание:** Доступ к полному device type (cached state) -**Возвращает:** Native UE enum для device type -**Use case:** Debug information, detailed device classification - -### Управление lifecycle - -#### CleanupDeviceDetection() -```typescript -CleanupDeviceDetection(): void -``` -**Описание:** Очистка системы и отвязка delegates -**Когда вызывать:** При уничтожении компонента -**Эффекты:** UnbindEvent, reset initialization state - -### Testing и debug - -#### ForceDeviceDetection() -```typescript -ForceDeviceDetection(): void -``` -**Описание:** Принудительная повторная детекция устройства -**Use case:** Testing, debugging device state - -## Система тестирования - -### FT_InputDeviceDetection (Basic Functionality) -**Покрывает:** -- Успешность инициализации (`IsInitialized = true`) -- Корректность device queries (`IsKeyboard()` XOR `IsGamepad()`) -- Консистентность cached state с actual device -- Initial device detection работает - -### FT_InputDeviceEvents (Event Handling) -**Покрывает:** -- Event binding и registration -- Manual event triggering через `ExecuteIfBound()` -- Device state transitions при events -- Event handling без errors - -### FT_InputDeviceDebouncing (Performance) -**Покрывает:** -- Rapid event filtering (10 events → ≤1 change) -- Cooldown timing accuracy -- No memory leaks при intensive events -- Performance под нагрузкой - -### Test Coverage -```typescript -TestScenarios = [ - 'Инициализация с correct delegate binding', - 'Initial device detection работает', - 'IsKeyboard/IsGamepad consistency проверки', - 'Manual event firing changes device state', - 'Rapid events properly debounced', - 'Cleanup properly unbinds delegates', - 'Toast notifications при device changes', - 'Performance при intensive event load' -] -``` - -## Интеграция с Main Character - -### Blueprint Integration -```typescript -// В BP_MainCharacter EventBeginPlay: -EventBeginPlay() → - Initialize Toast System → - Initialize Input Device Detection → - Initialize Other Systems... - -// В custom events для UI updates: -OnNeedUIUpdate() → - Get Input Device Component → IsGamepad() → - Branch: Update UI Prompts accordingly -``` - -### Component References -```typescript -// В BP_MainCharacter variables: -Components: -├─ Input Device Component (AC_InputDevice) -├─ Toast System Component (AC_ToastSystem) -├─ Debug HUD Component (AC_DebugHUD) -└─ Movement Component (AC_Movement) -``` - -## Файловая структура - -``` -Content/ -├── Input/ -│ ├── Components/ -│ │ └── AC_InputDevice.ts # Main component -│ └── Tests/ -│ ├── FT_InputDeviceDetection.ts # Basic functionality -│ ├── FT_InputDeviceEvents.ts # Event handling -│ └── FT_InputDeviceDebouncing.ts # Performance testing -├── UE/ (Native UE wrappers) -│ ├── InputDeviceSubsystem.ts # Event delegate wrapper -│ ├── HardwareDeviceIdentifier.ts # UE device info struct -│ └── EHardwareDevicePrimaryType.ts # UE device enum -├── Debug/ -│ └── Components/AC_DebugHUD.ts # Integration for debug page -└── Blueprints/ - └── BP_MainCharacter.ts # Main integration point -``` - -## Best Practices - -### Использование в коде -```typescript -// ✅ Хорошо - simple binary checks -if (this.InputDeviceComponent.IsGamepad()) { - this.SetGamepadUI(); -} else { - this.SetKeyboardUI(); -} - -// ✅ Хорошо - proper initialization order -EventBeginPlay() → - InitializeToastSystem() → - InitializeDeviceDetection() → - InitializeOtherSystems() - -// ✅ Хорошо - cleanup в EndPlay -EventEndPlay() → - this.InputDeviceComponent.CleanupDeviceDetection() - -// ❌ Плохо - checking device type каждый Tick -EventTick() → - this.InputDeviceComponent.IsGamepad() // Wasteful! - -// ✅ Хорошо - cache result или use events -OnDeviceChanged() → - this.CachedIsGamepad = this.InputDeviceComponent.IsGamepad() -``` - -### Performance recommendations -- **Cache device checks** если нужно в hot paths -- **Use event-driven UI updates** вместо polling в Tick -- **Initialize early** в BeginPlay для immediate availability -- **Cleanup properly** для предотвращения delegate leaks - -## Известные ограничения - -### Текущие ограничения -1. **Binary classification only** - только Gamepad vs KeyboardMouse -2. **UE 5.3+ requirement** - OnInputHardwareDeviceChanged delegate -3. **Single device focus** - нет multi-user support -4. **Basic debouncing** - фиксированный 300ms cooldown - -### Архитектурные решения -- **Event-driven tradeoff:** Зависимость от UE delegate system -- **Binary simplicity:** Covers 99% game use cases -- **Fixed debouncing:** Простота важнее configurability -- **Toast integration:** Debug notifications не essential для core functionality - -### Известные edge cases -- **Device disconnection:** Может не trigger event немедленно -- **Multiple gamepads:** Нет differentiation между controller 1 vs 2 -- **Specialized hardware:** Racing wheels, flight sticks = "keyboard" - -## Планы развития (при необходимости) - -### Stage 6+: Enhanced Input Integration -1. **Automatic Input Mapping Context switching** based на device type -2. **Device-specific action bindings** (разные кнопки для разных геймпадов) -3. **Multi-user device tracking** для split-screen scenarios - -### Долгосрочные улучшения -1. **Configurable debouncing** через Project Settings -2. **Device-specific sub-classification** (Xbox vs PlayStation controllers) -3. **Device capability queries** (rumble support, gyro, etc.) -4. **Cross-platform consistency** improvements - -### Принцип расширения -- **Preserve binary simplicity** как primary API -- **Add specialized methods** для advanced use cases -- **Maintain event-driven approach** для consistency -- **Keep zero polling overhead** для performance - -## Заключение - -Input Device Detection System представляет собой event-driven обертку над Unreal Engine InputDeviceSubsystem, обеспечивающую простую бинарную классификацию устройств ввода с automatic debouncing и zero polling overhead. - -**Ключевые достижения:** -- ✅ **Event-driven architecture:** Zero overhead при отсутствии device switching -- ✅ **Automatic debouncing:** Built-in защита от flickering и rapid switching -- ✅ **Binary simplicity:** IsGamepad() vs IsKeyboard() покрывает 99% use cases -- ✅ **UE 5.3+ integration:** Использование latest InputDeviceSubsystem features -- ✅ **Production ready:** Comprehensive testing и clean integration points -- ✅ **Toast integration:** Debug notifications для development convenience - -**Архитектурные преимущества:** -- Event-driven design eliminates polling overhead completely -- Cached state обеспечивает instant access к device information -- Automatic debouncing решает stick drift и hardware timing issues -- Clean integration с existing Toast и Debug systems -- Ready для Enhanced Input integration в следующих этапах - -**Performance characteristics:** -- Zero CPU overhead при отсутствии device switching -- <0.05ms processing time per device change event -- Instant device state queries через cached values -- Minimal memory footprint (~50 bytes total state) - -Система готова к использованию в production и provides solid foundation для Enhanced Input integration в будущих этапах разработки. diff --git a/Content/Input/Tests/FT_InputDeviceDetection.ts b/Content/Input/Tests/FT_InputDeviceDetection.ts deleted file mode 100644 index 850d870..0000000 --- a/Content/Input/Tests/FT_InputDeviceDetection.ts +++ /dev/null @@ -1,133 +0,0 @@ -// Input/Tests/FT_InputDeviceDetection.ts - -import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; - -/** - * Functional Test: Input Device Detection System - * Tests event-driven device detection with minimal wrapper approach - * Validates initialization, device queries, and delegate events - */ -export class FT_InputDeviceDetection extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test entry point - validates complete device detection workflow - * Tests initialization, device queries, and simulated device changes - */ - EventStartTest(): void { - // Initialize components - this.ToastSystemComponent.InitializeToastSystem(); - this.InputDeviceComponent.InitializeDeviceDetection( - this.ToastSystemComponent, - this.DebugHUDComponent - ); - this.TestInitialization(); - this.TestDeviceQueries(); - this.FinishTest(EFunctionalTestResult.Succeeded); - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // TEST METHODS - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Test initialization and initial device detection - * @returns True if test passed - */ - private TestInitialization(): void { - // Validate initialization - if (this.InputDeviceComponent.IsInitialized) { - if ( - this.InputDeviceComponent.GetCurrentInputDevice() !== - EHardwareDevicePrimaryType.Unspecified - ) { - // Test passed - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'No initial device detected' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Input Device Detection failed to initialize' - ); - } - } - - /** - * Test device query functions consistency - * @returns True if test passed - */ - private TestDeviceQueries(): void { - const currentDevice = this.InputDeviceComponent.GetCurrentInputDevice(); - const isKeyboard = this.InputDeviceComponent.IsKeyboard(); - const isGamepad = this.InputDeviceComponent.IsGamepad(); - - // Validate that exactly one device type is active - if (!(isKeyboard && isGamepad)) { - if (isKeyboard || isGamepad) { - const expectedIsKeyboard = - currentDevice === EHardwareDevicePrimaryType.KeyboardAndMouse; - const expectedIsGamepad = - currentDevice === EHardwareDevicePrimaryType.Gamepad; - - if ( - isKeyboard === expectedIsKeyboard && - isGamepad === expectedIsGamepad - ) { - // Test passed - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Device query functions inconsistent with GetCurrentInputDevice()' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Neither keyboard nor gamepad detected' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Both keyboard and gamepad detected simultaneously' - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Input device detection system - component under test - * @category Components - */ - private InputDeviceComponent = new AC_InputDevice(); - - /** - * Toast notification system - required for device detection initialization - * @category Components - */ - private ToastSystemComponent = new AC_ToastSystem(); - - /** - * Debug HUD system - displays test status and parameters - * @category Components - */ - private DebugHUDComponent = new AC_DebugHUD(); -} diff --git a/Content/Input/Tests/FT_InputDeviceDetection.uasset b/Content/Input/Tests/FT_InputDeviceDetection.uasset deleted file mode 100644 index 1056743..0000000 --- a/Content/Input/Tests/FT_InputDeviceDetection.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72fa7c9daa368a973b530ad4539135be1771eac770cc78f62925e0d25b9ceddd -size 146508 diff --git a/Content/Levels/TestLevel.ts b/Content/Levels/TestLevel.ts index 4f45316..464aa05 100644 --- a/Content/Levels/TestLevel.ts +++ b/Content/Levels/TestLevel.ts @@ -1,59 +1,5 @@ -// Levels/TestLevel.ts +// Content/Levels/TestLevel.ts -import { BP_MainCharacter } from '#root/Blueprints/BP_MainCharacter.ts'; -import { FT_CameraInitialization } from '#root/Camera/Tests/FT_CameraInitialization.ts'; -import { FT_CameraLimits } from '#root/Camera/Tests/FT_CameraLimits.ts'; -import { FT_CameraRotation } from '#root/Camera/Tests/FT_CameraRotation.ts'; -import { FT_CameraSensitivity } from '#root/Camera/Tests/FT_CameraSensitivity.ts'; -import { FT_CameraSmoothing } from '#root/Camera/Tests/FT_CameraSmoothing.ts'; -import { FT_DebugNavigation } from '#root/Debug/Tests/FT_DebugNavigation.ts'; -import { FT_DebugPageManagement } from '#root/Debug/Tests/FT_DebugPageManagement.ts'; -import { FT_DebugSystem } from '#root/Debug/Tests/FT_DebugSystem.ts'; -import { FT_InputDeviceDetection } from '#root/Input/Tests/FT_InputDeviceDetection.ts'; -import { FT_ToastLimit } from '#root/Toasts/Tests/FT_ToastLimit.ts'; -import { FT_ToastsDurationHandling } from '#root/Toasts/Tests/FT_ToastsDurationHandling.ts'; -import { FT_ToastsEdgeCases } from '#root/Toasts/Tests/FT_ToastsEdgeCases.ts'; -import { FT_ToastsSystemInitialization } from '#root/Toasts/Tests/FT_ToastsSystemInitialization.ts'; -import { FT_ToastsToastCreation } from '#root/Toasts/Tests/FT_ToastsToastCreation.ts'; +import { BP_MainCharacter } from '/Content/Blueprints/BP_MainCharacter.ts'; new BP_MainCharacter(); - -// Camera Tests -const CameraInitializationTest = new FT_CameraInitialization(); -const CameraLimitsTest = new FT_CameraLimits(); -const CameraRotationTest = new FT_CameraRotation(); -const CameraSensitivityTest = new FT_CameraSensitivity(); -const CameraSmoothingTest = new FT_CameraSmoothing(); - -CameraInitializationTest.EventStartTest(); -CameraLimitsTest.EventStartTest(); -CameraRotationTest.EventStartTest(); -CameraSensitivityTest.EventStartTest(); -CameraSmoothingTest.EventStartTest(); - -// Debug Tests -const DebugNavigationTest = new FT_DebugNavigation(); -const DebugSystemTest = new FT_DebugSystem(); -const DebugPageManagementTest = new FT_DebugPageManagement(); - -DebugNavigationTest.EventStartTest(); -DebugSystemTest.EventStartTest(); - -// Input Tests -const InputDeviceDetectionTest = new FT_InputDeviceDetection(); - -InputDeviceDetectionTest.EventStartTest(); - -// Toasts Tests -const ToastLimitsTest = new FT_ToastLimit(); -const ToastsDurationHandlingTest = new FT_ToastsDurationHandling(); -const ToastsEdgeCasesTest = new FT_ToastsEdgeCases(); -const ToastsSystemInitializationTest = new FT_ToastsSystemInitialization(); -const ToastsToastCreationTest = new FT_ToastsToastCreation(); - -ToastLimitsTest.EventStartTest(); -ToastsDurationHandlingTest.EventStartTest(); -ToastsEdgeCasesTest.EventStartTest(); -ToastsSystemInitializationTest.EventStartTest(); -ToastsToastCreationTest.EventStartTest(); -DebugPageManagementTest.EventStartTest(); diff --git a/Content/Levels/TestLevel.umap b/Content/Levels/TestLevel.umap index f346e5b..8ac2e4c 100644 --- a/Content/Levels/TestLevel.umap +++ b/Content/Levels/TestLevel.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb914c01745f85a6d596cdfee1c607a88bf0a32f7259606afe573c4620e49de7 -size 97997 +oid sha256:050c1016d489a850f60541762f9fb12a9e35e46b0c54f780570364c3d4d47250 +size 573337 diff --git a/Content/Math/Libraries/BFL_Vectors.ts b/Content/Math/Libraries/BFL_Vectors.ts deleted file mode 100644 index e99a4cd..0000000 --- a/Content/Math/Libraries/BFL_Vectors.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Math/Libraries/BFL_Vectors.ts - -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import type { Float } from '#root/UE/Float.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -/** - * Blueprint Function Library: Vector Mathematics - * Pure mathematical functions for vector operations and surface angle calculations - * Used by movement system for deterministic surface classification - */ -export class BFL_VectorsClass extends BlueprintFunctionLibrary { - constructor( - outer: null | BlueprintFunctionLibrary = null, - name: string = 'BFL_Vectors' - ) { - super(outer, name); - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // FUNCTIONS - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Calculate angle between two normalized vectors - * @param Vector1 - First normalized vector - * @param Vector2 - Second normalized vector - * @returns Angle between vectors in radians (0 to π) - * @example - * // 90° angle between X and Z axes - * GetAngleBetweenVectors(new Vector(1,0,0), new Vector(0,0,1)) // returns π/2 - */ - public GetAngleBetweenVectors(Vector1: Vector, Vector2: Vector): Float { - /** - * Internal calculation using dot product and arccosine - */ - const CalculateAngleBetweenVectors = (v1: Vector, v2: Vector): Float => - MathLibrary.Acos(MathLibrary.Dot(v1, v2)); - - return CalculateAngleBetweenVectors(Vector1, Vector2); - } - - /** - * Generate surface normal vector from angle in degrees - * @param AngleDegrees - Angle from horizontal in degrees (0-180) - * @returns Normalized surface normal vector - * @example - * // Flat surface (0°) - * GetNormalFromAngle(0) // returns Vector(0,0,1) - * // Vertical wall (90°) - * GetNormalFromAngle(90) // returns Vector(1,0,0) - */ - public GetNormalFromAngle(AngleDegrees: Float): Vector { - /** - * Calculate X component using sine of angle - */ - const CalculateX = (angle: Float): Float => - MathLibrary.Sin(MathLibrary.DegreesToRadians(angle)); - - /** - * Calculate Z component using cosine of angle - */ - const CalculateZ = (angle: Float): Float => - MathLibrary.Cos(MathLibrary.DegreesToRadians(angle)); - - return new Vector(CalculateX(AngleDegrees), 0, CalculateZ(AngleDegrees)); - } - - /** - * Calculate angle between surface normal and up vector - * @param SurfaceNormal - Normalized surface normal vector - * @returns Angle from horizontal plane in radians (0 = flat, π/2 = vertical) - * @example - * // Flat surface - * GetSurfaceAngle(new Vector(0,0,1)) // returns 0 - * // Vertical wall - * GetSurfaceAngle(new Vector(1,0,0)) // returns π/2 - */ - public GetSurfaceAngle(SurfaceNormal: Vector): Float { - return this.GetAngleBetweenVectors(SurfaceNormal, new Vector(0, 0, 1)); - } -} - -export const BFL_Vectors = new BFL_VectorsClass(); diff --git a/Content/Math/Libraries/BFL_Vectors.uasset b/Content/Math/Libraries/BFL_Vectors.uasset deleted file mode 100644 index 3efffef..0000000 --- a/Content/Math/Libraries/BFL_Vectors.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d9e0f89814abc8e644f763ebdc906075af6dfa8d9b62f26a32478ed89b96517b -size 55476 diff --git a/Content/Movement/AC_Movement.ts b/Content/Movement/AC_Movement.ts deleted file mode 100644 index 419fca7..0000000 --- a/Content/Movement/AC_Movement.ts +++ /dev/null @@ -1,231 +0,0 @@ -// Movement/AC_Movement.ts - -import type { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; -import { BFL_MovementProcessor } from '#root/Movement/Core/BFL_MovementProcessor.ts'; -import type { DA_MovementConfig } from '#root/Movement/Core/DA_MovementConfig.ts'; -import { DA_MovementConfigDefault } from '#root/Movement/Core/DA_MovementConfigDefault.ts'; -import { E_MovementState } from '#root/Movement/Core/E_MovementState.ts'; -import type { S_MovementState } from '#root/Movement/Core/S_MovementState.ts'; -import { E_SurfaceType } from '#root/Movement/Surface/E_SurfaceType.ts'; -import { S_AngleThresholds } from '#root/Movement/Surface/S_AngleThresholds.ts'; -import { ActorComponent } from '#root/UE/ActorComponent.ts'; -import type { CapsuleComponent } from '#root/UE/CapsuleComponent.ts'; -import type { Float } from '#root/UE/Float.ts'; -import { HitResult } from '#root/UE/HitResult.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { Rotator } from '#root/UE/Rotator.ts'; -import { StringLibrary } from '#root/UE/StringLibrary.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -/** - * Movement System Component - * Core deterministic movement system for 3D platformer - * Handles surface classification and movement physics calculations - */ -export class AC_Movement extends ActorComponent { - // ════════════════════════════════════════════════════════════════════════════════════════ - // FUNCTIONS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // Debug - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Update debug HUD with current movement info - * @category Debug - */ - public UpdateDebugPage(): void { - if (SystemLibrary.IsValid(this.DebugHUDComponent)) { - if ( - this.DebugHUDComponent.ShouldUpdatePage( - this.DebugPageID, - SystemLibrary.GetGameTimeInSeconds() - ) - ) { - this.DebugHUDComponent.UpdatePageContent( - this.DebugPageID, - // Constants - `Max Speed: ${this.Config.MaxSpeed}\n` + - `Acceleration: ${this.Config.Acceleration}\n` + - `Friction: ${this.Config.Friction}\n` + - `Gravity: ${this.Config.Gravity}\n` + - `Initialized: ${this.IsInitialized}\n` + - `\n` + - // Current State - `Current Velocity: ${StringLibrary.ConvVectorToString(this.CurrentMovementState.Velocity)}\n` + - `Speed: ${this.CurrentMovementState.Speed}\n` + - `Is Grounded: ${this.CurrentMovementState.IsGrounded}\n` + - `Surface Type: ${this.CurrentMovementState.SurfaceType}\n` + - `Movement State: ${this.CurrentMovementState.MovementState}\n` + - `Input Magnitude: ${this.CurrentMovementState.InputMagnitude}` + - `\n` + - // Rotation - `Current Yaw: ${this.CurrentMovementState.Rotation.yaw}\n` + - `Rotation Delta: ${this.CurrentMovementState.RotationDelta}\n°` + - `Is Rotating: ${this.CurrentMovementState.IsRotating}\n` + - `Rotation Speed: ${this.Config.RotationSpeed}\n°` + - `Min Speed: ${this.Config.MinSpeedForRotation}` + - `\n` + - // Position - `Location: ${StringLibrary.ConvVectorToString(this.GetOwner().GetActorLocation())}` + - `\n` + - // Sweep Collision - `Collision Checks: ${this.CurrentMovementState.CollisionCount}/${this.Config.MaxCollisionChecks}\n` + - `Sweep Blocked: ${this.CurrentMovementState.IsBlocked}\n` + - `Ground Distance: ${this.Config.GroundTraceDistance} cm` - ); - } - } - } - - // ──────────────────────────────────────────────────────────────────────────────────────── - // Default - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Process movement input from player controller - * Normalizes input and calculates target velocity - * @param InputVector - Raw input from WASD/gamepad stick - * @param DeltaTime - Time since last frame for frame-rate independence - * @category Default - */ - public ProcessMovementInput(InputVector: Vector, DeltaTime: Float): void { - if (this.IsInitialized) { - this.CurrentMovementState = BFL_MovementProcessor.ProcessMovement( - this.CurrentMovementState, - { - InputVector, - DeltaTime, - CapsuleComponent: this.CapsuleComponent, - Config: this.Config, - AngleThresholdsRads: this.AngleThresholdsRads, - }, - SystemLibrary.IsValid(this.DebugHUDComponent) - ? this.DebugHUDComponent.ShowVisualDebug - : false - ); - - this.GetOwner().SetActorLocation(this.CurrentMovementState.Location); - this.GetOwner().SetActorRotation(this.CurrentMovementState.Rotation); - } - } - - /** - * Initialize movement system with angle conversion - * Converts degree thresholds to radians for runtime performance - * @category Default - */ - public InitializeMovementSystem( - CapsuleComponentRef: CapsuleComponent | null = null, - DebugHUDComponentRef: AC_DebugHUD | null = null - ): void { - this.CapsuleComponent = CapsuleComponentRef; - this.DebugHUDComponent = DebugHUDComponentRef; - this.IsInitialized = true; - - this.AngleThresholdsRads = { - Walkable: MathLibrary.DegreesToRadians( - this.Config.AngleThresholdsDegrees.Walkable - ), - SteepSlope: MathLibrary.DegreesToRadians( - this.Config.AngleThresholdsDegrees.SteepSlope - ), - Wall: MathLibrary.DegreesToRadians( - this.Config.AngleThresholdsDegrees.Wall - ), - }; - - this.CurrentMovementState = BFL_MovementProcessor.CreateInitialState( - this.GetOwner().GetActorLocation(), - this.GetOwner().GetActorRotation() - ); - - if (SystemLibrary.IsValid(this.DebugHUDComponent)) { - this.DebugHUDComponent.AddDebugPage( - this.DebugPageID, - 'Movement Info', - 60 - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // Components - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Reference to debug HUD component for displaying camera info - * Optional, used for debugging purposes - * @category Components - */ - private DebugHUDComponent: AC_DebugHUD | null = null; - - /** - * Reference to character's capsule component for collision detection - * @category Components - */ - private CapsuleComponent: CapsuleComponent | null = null; - - // ──────────────────────────────────────────────────────────────────────────────────────── - // Default - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Default movement state - * @category Default - */ - private CurrentMovementState: S_MovementState = { - Location: new Vector(0, 0, 0), - Rotation: new Rotator(0, 0, 0), - Velocity: new Vector(0, 0, 0), - Speed: 0.0, - IsGrounded: false, - GroundHit: new HitResult(), - SurfaceType: E_SurfaceType.None, - IsBlocked: false, - CollisionCount: 0, - IsRotating: false, - RotationDelta: 0.0, - MovementState: E_MovementState.Idle, - InputMagnitude: 0.0, - }; - - /** - * Default movement configuration - * @category Default - * @instanceEditable true - */ - private readonly Config: DA_MovementConfig = new DA_MovementConfigDefault(); - - /** - * Runtime cached angle thresholds in radians - * Converted from degrees during initialization for performance - * @category Default - */ - private AngleThresholdsRads: S_AngleThresholds = { - Walkable: 0.0, - SteepSlope: 0.0, - Wall: 0.0, - }; - - /** - * Debug page identifier for organizing debug output - * Used by debug HUD to categorize information - * @category Default - * @instanceEditable true - */ - private readonly DebugPageID: string = 'MovementInfo'; - - /** - * Flag indicating if movement system has been initialized - * Ensures angle thresholds are converted before use - * @category Debug - */ - private IsInitialized = false; -} diff --git a/Content/Movement/AC_Movement.uasset b/Content/Movement/AC_Movement.uasset deleted file mode 100644 index 6cba2a2..0000000 --- a/Content/Movement/AC_Movement.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:765ead06fe3a3f7cb4908016a988a9bfb697fa80bffb95eee24c1393a308328d -size 272377 diff --git a/Content/Movement/Collision/BFL_CollisionResolver.ts b/Content/Movement/Collision/BFL_CollisionResolver.ts deleted file mode 100644 index 19f2b5c..0000000 --- a/Content/Movement/Collision/BFL_CollisionResolver.ts +++ /dev/null @@ -1,325 +0,0 @@ -// Movement/Collision/BFL_CollisionResolver.ts - -import type { S_SweepResult } from '#root/Movement/Collision/S_SweepResult.ts'; -import { DA_MovementConfig } from '#root/Movement/Core/DA_MovementConfig.ts'; -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import type { CapsuleComponent } from '#root/UE/CapsuleComponent.ts'; -import { EDrawDebugTrace } from '#root/UE/EDrawDebugTrace.ts'; -import { ETraceTypeQuery } from '#root/UE/ETraceTypeQuery.ts'; -import type { Float } from '#root/UE/Float.ts'; -import { HitResult } from '#root/UE/HitResult.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -/** - * Collision Resolution System - * - * Handles swept collision detection and surface sliding - * Prevents tunneling through geometry with adaptive stepping - * Provides deterministic collision response - * - * @category Movement Collision - * @impure Uses SystemLibrary traces (reads world state) - */ -class BFL_CollisionResolverClass extends BlueprintFunctionLibrary { - // ════════════════════════════════════════════════════════════════════════════════════════ - // SWEEP COLLISION - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Perform deterministic swept collision detection - * Breaks movement into adaptive steps to prevent tunneling - * Handles multiple collision iterations for smooth sliding - * - * @param StartLocation - Starting position for sweep - * @param DesiredDelta - Desired movement vector - * @param CapsuleComponent - Capsule for collision shape - * @param Config - Movement configuration with step sizes and iteration limits - * @param DeltaTime - Frame delta time for adaptive step calculation - * @param IsShowVisualDebug - Whether to draw debug traces in world - * @returns SweepResult with final location and collision info - * - * @example - * const result = CollisionResolver.PerformSweep( - * characterLocation, - * new Vector(100, 0, 0), // Move 1 meter forward - * capsuleComponent, - * config, - * 0.016 - * ); - * character.SetActorLocation(result.Location); - * - * @impure true - performs world traces - * @category Sweep Collision - */ - public PerformSweep( - StartLocation: Vector = new Vector(0, 0, 0), - DesiredDelta: Vector = new Vector(0, 0, 0), - CapsuleComponent: CapsuleComponent | null = null, - Config: DA_MovementConfig = new DA_MovementConfig(), - DeltaTime: Float = 0, - IsShowVisualDebug: boolean = false - ): S_SweepResult { - // Validate capsule component - if (SystemLibrary.IsValid(CapsuleComponent)) { - // Calculate total distance to travel - const totalDistance = MathLibrary.VectorLength(DesiredDelta); - - // Early exit if movement is negligible - if (totalDistance >= 0.01) { - // Calculate adaptive step size based on velocity - const stepSize = this.CalculateStepSize( - new Vector( - DesiredDelta.X / DeltaTime, - DesiredDelta.Y / DeltaTime, - DesiredDelta.Z / DeltaTime - ), - DeltaTime, - Config - ); - - // Perform stepped sweep - let currentLocation = StartLocation; - let remainingDistance = totalDistance; - let collisionCount = 0; - let lastHit = new HitResult(); - - // Calculate number of steps (capped by max collision checks) - const CalculateNumSteps = (maxCollisionChecks: Integer): Integer => - MathLibrary.Min( - MathLibrary.Ceil(totalDistance / stepSize), - maxCollisionChecks - ); - - for (let i = 0; i < CalculateNumSteps(Config.MaxCollisionChecks); i++) { - collisionCount++; - - // Calculate step distance (last step might be shorter) - const currentStepSize = MathLibrary.Min(stepSize, remainingDistance); - - const MathExpression = (desiredDelta: Vector): Vector => - new Vector( - currentLocation.X + - MathLibrary.Normal(desiredDelta).X * currentStepSize, - currentLocation.Y + - MathLibrary.Normal(desiredDelta).Y * currentStepSize, - currentLocation.Z + - MathLibrary.Normal(desiredDelta).Z * currentStepSize - ); - - // Calculate target position for this step - const targetLocation = MathExpression(DesiredDelta); - - // Perform capsule trace for this step - const { OutHit, ReturnValue } = SystemLibrary.CapsuleTraceByChannel( - currentLocation, - targetLocation, - CapsuleComponent.GetScaledCapsuleRadius(), - CapsuleComponent.GetScaledCapsuleHalfHeight(), - ETraceTypeQuery.Visibility, - false, - [], - IsShowVisualDebug - ? EDrawDebugTrace.ForDuration - : EDrawDebugTrace.None - ); - - // Check if trace hit something - if (ReturnValue) { - // Collision detected - return hit location - lastHit = OutHit; - - return { - Location: lastHit.Location, - Hit: lastHit, - Blocked: true, - CollisionCount: collisionCount, - }; - } else { - // No collision - update position and continue - currentLocation = targetLocation; - remainingDistance = remainingDistance - currentStepSize; - - // Check if reached destination - if (remainingDistance <= 0.01) { - break; - } - } - } - - // Reached destination without blocking hit - return { - Location: currentLocation, - Hit: lastHit, - Blocked: false, - CollisionCount: collisionCount, - }; - } else { - return { - Location: StartLocation, - Hit: new HitResult(), - Blocked: false, - CollisionCount: 0, - }; - } - } else { - return { - Location: StartLocation, - Hit: new HitResult(), - Blocked: false, - CollisionCount: 0, - }; - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // SURFACE SLIDING - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Project movement vector onto collision surface for sliding - * Removes component of movement that goes into surface - * Allows character to slide smoothly along walls - * - * @param MovementDelta - Desired movement vector - * @param SurfaceNormal - Normal of surface that was hit - * @returns Projected movement vector parallel to surface - * - * @example - * // Character hits wall at 45° angle - * const slideVector = CollisionResolver.ProjectOntoSurface( - * new Vector(100, 100, 0), // Moving diagonally - * new Vector(-0.707, 0, 0.707) // Wall normal (45° wall) - * ); - * // Returns vector parallel to wall surface - * - * @pure true - * @category Surface Sliding - */ - public ProjectOntoSurface( - MovementDelta: Vector, - SurfaceNormal: Vector - ): Vector { - // Project by removing normal component - // Formula: V' = V - (V·N)N - const MathExpression = ( - movementDelta: Vector, - surfaceNormal: Vector - ): Vector => - new Vector( - MovementDelta.X - - SurfaceNormal.X * MathLibrary.Dot(movementDelta, surfaceNormal), - MovementDelta.Y - - SurfaceNormal.Y * MathLibrary.Dot(movementDelta, surfaceNormal), - MovementDelta.Z - - SurfaceNormal.Z * MathLibrary.Dot(movementDelta, surfaceNormal) - ); - - return MathExpression(MovementDelta, SurfaceNormal); - } - - /** - * Calculate sliding vector after collision - * Combines sweep result with projection for smooth sliding - * - * @param SweepResult - Result from PerformSweep - * @param OriginalDelta - Original desired movement - * @param StartLocation - Starting location before sweep - * @returns Vector to apply for sliding movement - * - * @example - * const slideVector = CollisionResolver.CalculateSlideVector( - * sweepResult, - * desiredDelta, - * startLocation - * ); - * if (slideVector.Length() > 0.01) { - * character.SetActorLocation(sweepResult.Location + slideVector); - * } - * - * @pure true - * @category Surface Sliding - */ - public CalculateSlideVector( - SweepResult: S_SweepResult, - OriginalDelta: Vector, - StartLocation: Vector - ): Vector { - if (SweepResult.Blocked) { - const MathExpression = ( - sweepLocation: Vector, - startLocation: Vector, - originalDelta: Vector - ): Vector => - new Vector( - originalDelta.X - (sweepLocation.X - startLocation.X), - originalDelta.Y - (sweepLocation.Y - startLocation.Y), - originalDelta.Z - (sweepLocation.Z - startLocation.Z) - ); - - // Project remaining movement onto collision surface - return this.ProjectOntoSurface( - MathExpression(SweepResult.Location, StartLocation, OriginalDelta), - SweepResult.Hit.ImpactNormal - ); - } else { - // No sliding if no collision - return new Vector(0, 0, 0); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // ADAPTIVE STEPPING - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Calculate adaptive step size based on velocity - * Fast movement = smaller steps (more precise) - * Slow movement = larger steps (more performant) - * - * @param Velocity - Current movement velocity - * @param DeltaTime - Frame delta time - * @param Config - Movement configuration with min/max step sizes - * @returns Step size in cm, clamped between min and max - * - * @example - * const stepSize = CollisionResolver.CalculateStepSize( - * new Vector(1000, 0, 0), // Fast movement - * 0.016, - * config - * ); - * // Returns small step size for precise collision detection - * - * @pure true - * @category Adaptive Stepping - */ - public CalculateStepSize( - Velocity: Vector = new Vector(0, 0, 0), - DeltaTime: Float = 0, - Config: DA_MovementConfig = new DA_MovementConfig() - ): Float { - // Calculate distance traveled this frame - const frameDistance = - MathLibrary.VectorLength( - new Vector(Velocity.X, Velocity.Y, 0) // Horizontal distance only - ) * DeltaTime; - - // If moving very slowly, use max step size - if (frameDistance < Config.MinStepSize) { - return Config.MaxStepSize; - } - - // Clamp between min and max - return MathLibrary.ClampFloat( - // Calculate adaptive step size (half of frame distance) - // This ensures at least 2 checks per frame - frameDistance * 0.5, - Config.MinStepSize, - Config.MaxStepSize - ); - } -} - -export const BFL_CollisionResolver = new BFL_CollisionResolverClass(); diff --git a/Content/Movement/Collision/BFL_CollisionResolver.uasset b/Content/Movement/Collision/BFL_CollisionResolver.uasset deleted file mode 100644 index 96d2f5c..0000000 --- a/Content/Movement/Collision/BFL_CollisionResolver.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:64c84a701347023af201453d7531e042ffcf2b67daa328a928723b2e1b85b307 -size 342332 diff --git a/Content/Movement/Collision/BFL_GroundProbe.ts b/Content/Movement/Collision/BFL_GroundProbe.ts deleted file mode 100644 index 5dccc9c..0000000 --- a/Content/Movement/Collision/BFL_GroundProbe.ts +++ /dev/null @@ -1,221 +0,0 @@ -// Movement/Collision/BFL_GroundProbe.ts - -import { DA_MovementConfig } from '#root/Movement/Core/DA_MovementConfig.ts'; -import { BFL_SurfaceClassifier } from '#root/Movement/Surface/BFL_SurfaceClassifier.ts'; -import { E_SurfaceType } from '#root/Movement/Surface/E_SurfaceType.ts'; -import type { S_AngleThresholds } from '#root/Movement/Surface/S_AngleThresholds.ts'; -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import type { CapsuleComponent } from '#root/UE/CapsuleComponent.ts'; -import { EDrawDebugTrace } from '#root/UE/EDrawDebugTrace.ts'; -import { ETraceTypeQuery } from '#root/UE/ETraceTypeQuery.ts'; -import type { Float } from '#root/UE/Float.ts'; -import { HitResult } from '#root/UE/HitResult.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -class BFL_GroundProbeClass extends BlueprintFunctionLibrary { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GROUND DETECTION - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Check if character is standing on walkable ground - * Performs line trace downward from capsule bottom - * Validates surface type using SurfaceClassifier - * - * @param CharacterLocation - Current character world location - * @param CapsuleComponent - Character's capsule component for trace setup - * @param AngleThresholdsRads - Surface angle thresholds in radians - * @param Config - Movement configuration with trace distance - * @param IsShowVisualDebug - Whether to draw debug trace in world - * @returns HitResult with ground information, or empty hit if not grounded - * - * @example - * const groundHit = GroundProbe.CheckGround( - * characterLocation, - * capsuleComponent, - * angleThresholdsRads, - * config - * ); - * if (groundHit.BlockingHit) { - * // Character is on walkable ground - * } - * - * @impure true - performs world trace - * @category Ground Detection - */ - public CheckGround( - CharacterLocation: Vector = new Vector(0, 0, 0), - CapsuleComponent: CapsuleComponent | null = null, - AngleThresholdsRads: S_AngleThresholds = { - Walkable: 0, - SteepSlope: 0, - Wall: 0, - }, - Config: DA_MovementConfig = new DA_MovementConfig(), - IsShowVisualDebug: boolean = false - ): HitResult { - if (SystemLibrary.IsValid(CapsuleComponent)) { - const CalculateEndLocation = ( - currentZ: Float, - halfHeight: Float, - groundTraceDistance: Float - ): Float => currentZ - halfHeight - groundTraceDistance; - - const { OutHit: groundHit, ReturnValue } = - SystemLibrary.LineTraceByChannel( - CharacterLocation, - new Vector( - CharacterLocation.X, - CharacterLocation.Y, - CalculateEndLocation( - CharacterLocation.Z, - CapsuleComponent.GetScaledCapsuleHalfHeight(), - Config.GroundTraceDistance - ) - ), - ETraceTypeQuery.Visibility, - false, - [], - IsShowVisualDebug ? EDrawDebugTrace.ForDuration : EDrawDebugTrace.None - ); - - // Check if trace hit something - if (!ReturnValue) { - return new HitResult(); - } - - if ( - BFL_SurfaceClassifier.IsWalkable( - BFL_SurfaceClassifier.Classify( - groundHit.ImpactNormal, - AngleThresholdsRads - ) - ) - ) { - return groundHit; - } else { - return new HitResult(); - } - } else { - return new HitResult(); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // GROUND SNAPPING - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Calculate snapped location to keep character on ground - * Prevents character from floating slightly above ground - * Only snaps if within reasonable distance threshold - * - * @param CurrentLocation - Current character location - * @param GroundHit - Ground hit result from CheckGround - * @param CapsuleComponent - Character's capsule component - * @param SnapThreshold - Maximum distance to snap (default: 10 cm) - * @returns Snapped location or original location if too far - * - * @example - * const snappedLocation = GroundProbe.CalculateSnapLocation( - * currentLocation, - * groundHit, - * capsuleComponent, - * 10.0 - * ); - * character.SetActorLocation(snappedLocation); - * - * @pure true - only calculations, no side effects - * @category Ground Snapping - */ - public CalculateSnapLocation( - CurrentLocation: Vector, - GroundHit: HitResult, - CapsuleComponent: CapsuleComponent | null, - SnapThreshold: Float = 10.0 - ): Vector { - if (GroundHit.BlockingHit) { - if (SystemLibrary.IsValid(CapsuleComponent)) { - const correctZ = - GroundHit.Location.Z + CapsuleComponent.GetScaledCapsuleHalfHeight(); - - const CalculateZDifference = (currentLocZ: Float): Float => - MathLibrary.abs(currentLocZ - correctZ); - - const zDifference = CalculateZDifference(CurrentLocation.Z); - - const ShouldSnap = (groundTraceDistance: Float): boolean => - zDifference > 0.1 && zDifference < groundTraceDistance; - - if (ShouldSnap(SnapThreshold)) { - return new Vector(CurrentLocation.X, CurrentLocation.Y, correctZ); - } else { - return CurrentLocation; - } - } else { - return CurrentLocation; - } - } else { - return CurrentLocation; - } - } - - /** - * Check if ground snapping should be applied - * Helper method to determine if conditions are right for snapping - * - * @param CurrentVelocityZ - Current vertical velocity - * @param GroundHit - Ground hit result - * @param IsGrounded - Whether character is considered grounded - * @returns True if snapping should be applied - * - * @example - * if (GroundProbe.ShouldSnapToGround(velocity.Z, groundHit, isGrounded)) { - * const snappedLoc = GroundProbe.CalculateSnapLocation(...); - * character.SetActorLocation(snappedLoc); - * } - * - * @pure true - * @category Ground Snapping - */ - public ShouldSnapToGround( - CurrentVelocityZ: Float, - GroundHit: HitResult, - IsGrounded: boolean - ): boolean { - return IsGrounded && GroundHit.BlockingHit && CurrentVelocityZ <= 0; - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // UTILITIES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Get surface type from ground hit - * Convenience method combining trace result with classification - * - * @param GroundHit - Ground hit result - * @param AngleThresholdsRads - Surface angle thresholds in radians - * @returns Surface type classification - * - * @pure true - * @category Utilities - */ - public GetSurfaceType( - GroundHit: HitResult, - AngleThresholdsRads: S_AngleThresholds - ): E_SurfaceType { - if (!GroundHit.BlockingHit) { - return BFL_SurfaceClassifier.Classify( - GroundHit.ImpactNormal, - AngleThresholdsRads - ); - } else { - return E_SurfaceType.None; - } - } -} - -export const BFL_GroundProbe = new BFL_GroundProbeClass(); diff --git a/Content/Movement/Collision/BFL_GroundProbe.uasset b/Content/Movement/Collision/BFL_GroundProbe.uasset deleted file mode 100644 index 8c809c5..0000000 --- a/Content/Movement/Collision/BFL_GroundProbe.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41ba1929183ec2eb5541e2bf68471414c74afddfb84a773c200a1be26089ea1b -size 261830 diff --git a/Content/Movement/Collision/S_SweepResult.ts b/Content/Movement/Collision/S_SweepResult.ts deleted file mode 100644 index 7446109..0000000 --- a/Content/Movement/Collision/S_SweepResult.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Movement/Collision/S_SweepResult.ts - -import type { HitResult } from '#root/UE/HitResult.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import type { Vector } from '#root/UE/Vector.ts'; - -export interface S_SweepResult { - Location: Vector; - Hit: HitResult; - Blocked: boolean; - CollisionCount: Integer; -} diff --git a/Content/Movement/Collision/S_SweepResult.uasset b/Content/Movement/Collision/S_SweepResult.uasset deleted file mode 100644 index 9f93837..0000000 --- a/Content/Movement/Collision/S_SweepResult.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:39fb926f7b29250d254bc17a6077886016d5340783f35bb83bc679e0247d65c3 -size 10677 diff --git a/Content/Movement/Core/BFL_MovementProcessor.ts b/Content/Movement/Core/BFL_MovementProcessor.ts deleted file mode 100644 index debbb5a..0000000 --- a/Content/Movement/Core/BFL_MovementProcessor.ts +++ /dev/null @@ -1,261 +0,0 @@ -// Movement/Core/BFL_MovementProcessor.ts - -import { BFL_CollisionResolver } from '#root/Movement/Collision/BFL_CollisionResolver.ts'; -import { BFL_GroundProbe } from '#root/Movement/Collision/BFL_GroundProbe.ts'; -import { E_MovementState } from '#root/Movement/Core/E_MovementState.ts'; -import type { S_MovementInput } from '#root/Movement/Core/S_MovementInput.ts'; -import type { S_MovementState } from '#root/Movement/Core/S_MovementState.ts'; -import { BFL_Kinematics } from '#root/Movement/Physics/BFL_Kinematics.ts'; -import { BFL_RotationController } from '#root/Movement/Rotation/BFL_RotationController.ts'; -import { BFL_MovementStateMachine } from '#root/Movement/State/BFL_MovementStateMachine.ts'; -import { BFL_SurfaceClassifier } from '#root/Movement/Surface/BFL_SurfaceClassifier.ts'; -import { E_SurfaceType } from '#root/Movement/Surface/E_SurfaceType.ts'; -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import { HitResult } from '#root/UE/HitResult.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import type { Rotator } from '#root/UE/Rotator.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -/** - * Movement Processor - * - * Unified movement processing system - * Takes current state + input, returns next state - * Pure functional approach - no side effects - * - * @category Movement Processing - * @impure Only collision traces (GroundProbe, CollisionResolver) - */ -class BFL_MovementProcessorClass extends BlueprintFunctionLibrary { - // ════════════════════════════════════════════════════════════════════════════════════════ - // MAIN PROCESSING - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Process movement for one frame - * - * Main entry point - computes complete next state from current state + input - * Orchestrates all movement subsystems in correct order - * - * @param CurrentState - Current movement state - * @param Input - Movement input data (input vector, delta time, config, etc.) - * @param IsShowVisualDebug - Whether to show debug traces in the world - * @returns New movement state after processing - * - * @example - * const newState = BFL_MovementProcessor.ProcessMovement( - * this.CurrentMovementState, - * { - * InputVector: inputVector, - * DeltaTime: deltaTime, - * CapsuleComponent: this.CapsuleComponent, - * Config: this.Config, - * AngleThresholdsRads: this.AngleThresholdsRads - * } - * ); - * - * // Apply results - * this.GetOwner().SetActorLocation(newState.Location); - * this.GetOwner().SetActorRotation(newState.Rotation); - * - * @impure true - performs collision traces - * @category Main Processing - */ - public ProcessMovement( - CurrentState: S_MovementState, - Input: S_MovementInput, - IsShowVisualDebug: boolean = false - ): S_MovementState { - // ═══════════════════════════════════════════════════════════════════ - // PHASE 1: INPUT & ROTATION - // ═══════════════════════════════════════════════════════════════════ - - const inputMagnitude = MathLibrary.VectorLength(Input.InputVector); - - const rotationResult = BFL_RotationController.UpdateRotation( - CurrentState.Rotation, - Input.InputVector, - Input.Config, - Input.DeltaTime, - CurrentState.Speed - ); - - // ═══════════════════════════════════════════════════════════════════ - // PHASE 2: GROUND DETECTION - // ═══════════════════════════════════════════════════════════════════ - - const groundHit = BFL_GroundProbe.CheckGround( - CurrentState.Location, - Input.CapsuleComponent, - Input.AngleThresholdsRads, - Input.Config - ); - - const isGrounded = groundHit.BlockingHit; - - const surfaceType = BFL_GroundProbe.GetSurfaceType( - groundHit, - Input.AngleThresholdsRads - ); - - // ═══════════════════════════════════════════════════════════════════ - // PHASE 3: PHYSICS CALCULATION - // ═══════════════════════════════════════════════════════════════════ - - let newVelocity = CurrentState.Velocity; - - // Ground movement or air friction - if (BFL_SurfaceClassifier.IsWalkable(surfaceType) && isGrounded) { - newVelocity = BFL_Kinematics.CalculateGroundVelocity( - newVelocity, - Input.InputVector, - Input.DeltaTime, - Input.Config - ); - } else { - newVelocity = BFL_Kinematics.CalculateFriction( - newVelocity, - Input.DeltaTime, - Input.Config - ); - } - - // Apply gravity - newVelocity = BFL_Kinematics.CalculateGravity( - newVelocity, - isGrounded, - Input.Config - ); - - const newSpeed = BFL_Kinematics.GetHorizontalSpeed(newVelocity); - - // ═══════════════════════════════════════════════════════════════════ - // PHASE 4: MOVEMENT APPLICATION (Sweep) - // ═══════════════════════════════════════════════════════════════════ - - const desiredDelta = new Vector( - newVelocity.X * Input.DeltaTime, - newVelocity.Y * Input.DeltaTime, - newVelocity.Z * Input.DeltaTime - ); - - const sweepResult = BFL_CollisionResolver.PerformSweep( - CurrentState.Location, - desiredDelta, - Input.CapsuleComponent, - Input.Config, - Input.DeltaTime, - IsShowVisualDebug - ); - - let finalLocation = sweepResult.Location; - - // Handle collision sliding - if (sweepResult.Blocked) { - const slideVector = BFL_CollisionResolver.CalculateSlideVector( - sweepResult, - desiredDelta, - CurrentState.Location - ); - - if ( - MathLibrary.VectorLength(slideVector) > 0.5 && - MathLibrary.Dot( - MathLibrary.Normal(slideVector), - sweepResult.Hit.ImpactNormal - ) >= -0.1 - ) { - finalLocation = new Vector( - sweepResult.Location.X + slideVector.X, - sweepResult.Location.Y + slideVector.Y, - sweepResult.Location.Z + slideVector.Z - ); - } - } - - // ═══════════════════════════════════════════════════════════════════ - // PHASE 5: GROUND SNAPPING - // ═══════════════════════════════════════════════════════════════════ - - if ( - BFL_GroundProbe.ShouldSnapToGround(newVelocity.Z, groundHit, isGrounded) - ) { - finalLocation = BFL_GroundProbe.CalculateSnapLocation( - finalLocation, - groundHit, - Input.CapsuleComponent, - Input.Config.GroundTraceDistance - ); - } - - // ═══════════════════════════════════════════════════════════════════ - // PHASE 6: STATE DETERMINATION - // ═══════════════════════════════════════════════════════════════════ - - const movementState = BFL_MovementStateMachine.DetermineState({ - IsGrounded: isGrounded, - SurfaceType: surfaceType, - InputMagnitude: inputMagnitude, - CurrentSpeed: newSpeed, - VerticalVelocity: newVelocity.Z, - IsBlocked: sweepResult.Blocked, - }); - - // ═══════════════════════════════════════════════════════════════════ - // RETURN NEW STATE - // ═══════════════════════════════════════════════════════════════════ - - return { - Location: finalLocation, - Rotation: rotationResult.Rotation, - Velocity: newVelocity, - Speed: newSpeed, - IsGrounded: isGrounded, - GroundHit: groundHit, - SurfaceType: surfaceType, - IsBlocked: sweepResult.Blocked, - CollisionCount: sweepResult.CollisionCount, - IsRotating: rotationResult.IsRotating, - RotationDelta: rotationResult.RemainingDelta, - MovementState: movementState, - InputMagnitude: inputMagnitude, - }; - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // STATE UTILITIES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Create initial movement state - * - * @param Location - Starting location - * @param Rotation - Starting rotation - * @returns Initial movement state with defaults - * - * @pure true - * @category State Utilities - */ - public CreateInitialState( - Location: Vector, - Rotation: Rotator - ): S_MovementState { - return { - Location, - Rotation, - Velocity: new Vector(0, 0, 0), - Speed: 0.0, - IsGrounded: false, - GroundHit: new HitResult(), - SurfaceType: E_SurfaceType.None, - IsBlocked: false, - CollisionCount: 0, - IsRotating: false, - RotationDelta: 0.0, - MovementState: E_MovementState.Idle, - InputMagnitude: 0.0, - }; - } -} - -export const BFL_MovementProcessor = new BFL_MovementProcessorClass(); diff --git a/Content/Movement/Core/BFL_MovementProcessor.uasset b/Content/Movement/Core/BFL_MovementProcessor.uasset deleted file mode 100644 index d2ec171..0000000 --- a/Content/Movement/Core/BFL_MovementProcessor.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1f1fc746c4f721ea062b580f366ebd5273ea9799c9db8e9d918718553f07741 -size 478506 diff --git a/Content/Movement/Core/DA_MovementConfig.ts b/Content/Movement/Core/DA_MovementConfig.ts deleted file mode 100644 index 07e5a1b..0000000 --- a/Content/Movement/Core/DA_MovementConfig.ts +++ /dev/null @@ -1,149 +0,0 @@ -// Movement/Core/DA_MovementConfig.ts - -import { S_AngleThresholds } from '#root/Movement/Surface/S_AngleThresholds.ts'; -import type { Float } from '#root/UE/Float.ts'; -import { PrimaryDataAsset } from '#root/UE/PrimaryDataAsset.ts'; - -export class DA_MovementConfig extends PrimaryDataAsset { - // ════════════════════════════════════════════════════════════════════════════════════════ - // MOVEMENT PHYSICS - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Maximum horizontal movement speed in UE units per second - * Character cannot exceed this speed through ground movement - * Used as target velocity cap in ProcessGroundMovement - * - * @category Movement Physics - * @instanceEditable true - * @unit cm/s - */ - public readonly MaxSpeed: Float = 800.0; - - /** - * Speed of velocity interpolation towards target velocity - * Higher values = faster acceleration, more responsive feel - * Used with VInterpTo for smooth acceleration curves - * Value represents interpolation speed, not actual acceleration rate - * - * @category Movement Physics - * @instanceEditable true - */ - public readonly Acceleration: Float = 10.0; - - /** - * Speed of velocity interpolation towards zero when no input - * Higher values = faster stopping, less sliding - * Used with VInterpTo for smooth deceleration curves - * Should typically be <= Acceleration for natural feel - * - * @category Movement Physics - * @instanceEditable true - */ - public readonly Friction: Float = 8.0; - - /** - * Gravitational acceleration in UE units per second squared - * Applied to vertical velocity when character is airborne - * Standard Earth gravity ≈ 980 cm/s² in UE units - * Only affects Z-axis velocity, horizontal movement unaffected - * - * @category Movement Physics - * @instanceEditable true - * @unit cm/s^2 - */ - public readonly Gravity: Float = 980.0; - - // ════════════════════════════════════════════════════════════════════════════════════════ - // SURFACE DETECTION - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Surface classification angle thresholds in degrees - * Walkable ≤50°, SteepSlope ≤85°, Wall ≤95°, Ceiling >95° - * - * @category Surface Detection - * @instanceEditable true - */ - public readonly AngleThresholdsDegrees: S_AngleThresholds = { - Walkable: 50, - SteepSlope: 85, - Wall: 95, - }; - - // ════════════════════════════════════════════════════════════════════════════════════════ - // COLLISION SETTINGS - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Distance to trace downward for ground detection - * Should be slightly larger than capsule half-height - * - * @category Collision Settings - * @instanceEditable true - * @unit cm - */ - public readonly GroundTraceDistance: Float = 50.0; - - /** - * Minimum step size for collision sweeps - * Smaller values = more precise but more expensive - * - * @category Collision Settings - * @instanceEditable true - * @unit cm - */ - public readonly MinStepSize: Float = 1.0; - - /** - * Maximum step size for collision sweeps - * Larger values = less precise but cheaper - * - * @category Collision Settings - * @instanceEditable true - * @unit cm - */ - public readonly MaxStepSize: Float = 50.0; - - /** - * Maximum collision checks allowed per frame - * Prevents infinite loops in complex geometry - * - * @category Collision Settings - * @instanceEditable true - */ - public readonly MaxCollisionChecks: Float = 25; - - // ════════════════════════════════════════════════════════════════════════════════════════ - // CHARACTER ROTATION - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Character rotation speed (degrees per second) - * How fast character turns toward movement direction - * - * @category Character Rotation - * @instanceEditable true - * @unit deg/s - */ - public RotationSpeed: Float = 360.0; - - /** - * Minimum movement speed required to rotate character - * Prevents rotation jitter when nearly stationary - * - * @category Character Rotation - * @instanceEditable true - * @unit cm/s - */ - public MinSpeedForRotation: Float = 50.0; - - /** - * Enable/disable character rotation toward movement - * Useful for debugging or special movement modes - * - * @category Character Rotation - * @instanceEditable true - */ - public ShouldRotateToMovement: boolean = true; -} diff --git a/Content/Movement/Core/DA_MovementConfig.uasset b/Content/Movement/Core/DA_MovementConfig.uasset deleted file mode 100644 index 772461a..0000000 --- a/Content/Movement/Core/DA_MovementConfig.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a337a81732347aa5a77336f7d22b0b65cc0b7bfe627be0d61bd254e72236c16b -size 27889 diff --git a/Content/Movement/Core/DA_MovementConfigDefault.ts b/Content/Movement/Core/DA_MovementConfigDefault.ts deleted file mode 100644 index e7eaf31..0000000 --- a/Content/Movement/Core/DA_MovementConfigDefault.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Movement/Core/DA_MovementConfigDefault.ts - -import { DA_MovementConfig } from '#root/Movement/Core/DA_MovementConfig.ts'; - -export class DA_MovementConfigDefault extends DA_MovementConfig { - // ════════════════════════════════════════════════════════════════════════════════════════ - // MOVEMENT PHYSICS - // ════════════════════════════════════════════════════════════════════════════════════════ - - override MaxSpeed = 800.0; - override Acceleration = 10.0; - override Friction = 8.0; - override Gravity = 980.0; - - // ════════════════════════════════════════════════════════════════════════════════════════ - // SURFACE DETECTION - // ════════════════════════════════════════════════════════════════════════════════════════ - - override AngleThresholdsDegrees = { - Walkable: 50.0, - SteepSlope: 85.0, - Wall: 95.0, - }; - - // ════════════════════════════════════════════════════════════════════════════════════════ - // COLLISION SETTINGS - // ════════════════════════════════════════════════════════════════════════════════════════ - - override GroundTraceDistance = 50.0; - override MinStepSize = 1.0; - override MaxStepSize = 50.0; - override MaxCollisionChecks = 25; - - // ════════════════════════════════════════════════════════════════════════════════════════ - // CHARACTER ROTATION - // ════════════════════════════════════════════════════════════════════════════════════════ - - override RotationSpeed = 360.0; - override MinSpeedForRotation = 50.0; - override ShouldRotateToMovement = true; -} diff --git a/Content/Movement/Core/DA_MovementConfigDefault.uasset b/Content/Movement/Core/DA_MovementConfigDefault.uasset deleted file mode 100644 index 83e4fbe..0000000 --- a/Content/Movement/Core/DA_MovementConfigDefault.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89f38ddd818ea722de4aeb6dd0ea681ce5c8ac48f068d8304b54a0db712cd95c -size 2265 diff --git a/Content/Movement/Core/E_MovementState.ts b/Content/Movement/Core/E_MovementState.ts deleted file mode 100644 index fa81e0c..0000000 --- a/Content/Movement/Core/E_MovementState.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Movement/Core/E_MovementState.ts - -/** - * Movement state enumeration - * Defines all possible character movement states - * - * @category Movement Enums - */ -export enum E_MovementState { - /** - * Character is stationary on ground - * No input, no movement - */ - Idle = 'Idle', - - /** - * Character is moving on ground - * Has input and horizontal velocity - */ - Walking = 'Walking', - - /** - * Character is in the air - * Not touching ground, affected by gravity - */ - Airborne = 'Airborne', - - /** - * Character is sliding down steep slope - * On non-walkable surface (steep slope) - */ - Sliding = 'Sliding', - - /** - * Character is blocked by collision - * Hitting wall or ceiling - */ - Blocked = 'Blocked', -} diff --git a/Content/Movement/Core/E_MovementState.uasset b/Content/Movement/Core/E_MovementState.uasset deleted file mode 100644 index cc774ea..0000000 --- a/Content/Movement/Core/E_MovementState.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e21e3d57e5ad4f521d074a9162abaffc0491a27e007eec4f2d2655a19598a22 -size 3277 diff --git a/Content/Movement/Core/S_MovementInput.ts b/Content/Movement/Core/S_MovementInput.ts deleted file mode 100644 index 122f51b..0000000 --- a/Content/Movement/Core/S_MovementInput.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Movement/Core/S_MovementInput.ts - -import type { DA_MovementConfig } from '#root/Movement/Core/DA_MovementConfig.ts'; -import type { S_AngleThresholds } from '#root/Movement/Surface/S_AngleThresholds.ts'; -import type { CapsuleComponent } from '#root/UE/CapsuleComponent.ts'; -import type { Float } from '#root/UE/Float.ts'; -import type { Vector } from '#root/UE/Vector.ts'; - -/** - * Movement processing input data - * All data needed to compute next movement state - * - * @category Movement Input - */ -export interface S_MovementInput { - /** - * Player input vector (normalized XY direction) - */ - InputVector: Vector; - - /** - * Frame delta time (seconds) - */ - DeltaTime: Float; - - /** - * Character capsule component for collision - */ - CapsuleComponent: CapsuleComponent | null; - - /** - * Movement configuration - */ - Config: DA_MovementConfig; - - /** - * Angle thresholds in radians (for surface classification) - */ - AngleThresholdsRads: S_AngleThresholds; -} diff --git a/Content/Movement/Core/S_MovementInput.uasset b/Content/Movement/Core/S_MovementInput.uasset deleted file mode 100644 index 9e35683..0000000 --- a/Content/Movement/Core/S_MovementInput.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d3c7f4d0554f7d1b69f62767b0470bcb765c34b022bd3b5f6b40319bdf17cda -size 9044 diff --git a/Content/Movement/Core/S_MovementState.ts b/Content/Movement/Core/S_MovementState.ts deleted file mode 100644 index ff7d542..0000000 --- a/Content/Movement/Core/S_MovementState.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Movement/Core/S_MovementState.ts - -import type { E_MovementState } from '#root/Movement/Core/E_MovementState.ts'; -import type { E_SurfaceType } from '#root/Movement/Surface/E_SurfaceType.ts'; -import type { Float } from '#root/UE/Float.ts'; -import type { HitResult } from '#root/UE/HitResult.ts'; -import type { Rotator } from '#root/UE/Rotator.ts'; -import type { Vector } from '#root/UE/Vector.ts'; - -/** - * Complete movement state snapshot - * Immutable data structure representing full character movement state - * - * @category Movement State - */ -export interface S_MovementState { - // ═══════════════════════════════════════════════════════════════════ - // TRANSFORM - // ═══════════════════════════════════════════════════════════════════ - - /** - * Character world location - */ - Location: Vector; - - /** - * Character rotation (yaw only) - */ - Rotation: Rotator; - - // ═══════════════════════════════════════════════════════════════════ - // VELOCITY & PHYSICS - // ═══════════════════════════════════════════════════════════════════ - - /** - * Current velocity vector (cm/s) - */ - Velocity: Vector; - - /** - * Horizontal speed (cm/s) - */ - Speed: Float; - - // ═══════════════════════════════════════════════════════════════════ - // GROUND STATE - // ═══════════════════════════════════════════════════════════════════ - - /** - * Whether character is on walkable ground - */ - IsGrounded: boolean; - - /** - * Ground trace hit result - */ - GroundHit: HitResult; - - /** - * Current surface type - */ - SurfaceType: E_SurfaceType; - - // ═══════════════════════════════════════════════════════════════════ - // COLLISION STATE - // ═══════════════════════════════════════════════════════════════════ - - /** - * Whether movement was blocked by collision - */ - IsBlocked: boolean; - - /** - * Number of collision checks this frame - */ - CollisionCount: number; - - // ═══════════════════════════════════════════════════════════════════ - // ROTATION STATE - // ═══════════════════════════════════════════════════════════════════ - - /** - * Whether character is actively rotating - */ - IsRotating: boolean; - - /** - * Remaining angular distance to target (degrees) - */ - RotationDelta: Float; - - // ═══════════════════════════════════════════════════════════════════ - // MOVEMENT STATE - // ═══════════════════════════════════════════════════════════════════ - - /** - * Current movement state (Idle, Walking, Airborne, etc.) - */ - MovementState: E_MovementState; - - /** - * Input magnitude (0-1) - */ - InputMagnitude: Float; -} diff --git a/Content/Movement/Core/S_MovementState.uasset b/Content/Movement/Core/S_MovementState.uasset deleted file mode 100644 index 3dc04d0..0000000 --- a/Content/Movement/Core/S_MovementState.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:30158d74fcea09a75fe8666717dbaff59563e5aecf6622d7cb170796ad431a9e -size 20518 diff --git a/Content/Movement/DA_TengriMovementConfig.ts b/Content/Movement/DA_TengriMovementConfig.ts new file mode 100644 index 0000000..e8da967 --- /dev/null +++ b/Content/Movement/DA_TengriMovementConfig.ts @@ -0,0 +1,25 @@ +// Content/Movement/Core/DA_TengriMovementConfig.ts + +import { TengriMovementConfig } from '/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.ts'; + +export class DA_TengriMovementConfig extends TengriMovementConfig { + override MaxSpeed = 800.0; + override Acceleration = 2048.0; + override Friction = 8.0; + override Gravity = 980.0; + override RotationSpeed = 360.0; + override MinSpeedForRotation = 10.0; + override SteepSlopeSlideFactor = 0.0; + + override CapsuleRadius = 34.0; + override CapsuleHalfHeight = 88.0; + override MaxSlideIterations = 3; + override MaxStepHeight = 45.0; + + override GroundSnapDistance = 20.0; + override GroundSnapOffset = 0.15; + + override WalkableAngleDeg = 50.0; + override SteepSlopeAngleDeg = 85.0; + override WallAngleDeg = 95.0; +} diff --git a/Content/Movement/DA_TengriMovementConfig.uasset b/Content/Movement/DA_TengriMovementConfig.uasset new file mode 100644 index 0000000..14a51ce --- /dev/null +++ b/Content/Movement/DA_TengriMovementConfig.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df346aa688899f30326cc51868d2bed03ae0ff23e723fb85d5aa30a56777ea08 +size 1231 diff --git a/Content/Movement/ManualTestingChecklist.md b/Content/Movement/ManualTestingChecklist.md deleted file mode 100644 index ae674f1..0000000 --- a/Content/Movement/ManualTestingChecklist.md +++ /dev/null @@ -1,1120 +0,0 @@ -# Movement System - Manual Testing Checklist (Stage 9 Refactored) - -## Overview -Comprehensive manual testing procedures для Movement System после архитектурного рефакторинга на Pipeline Processor pattern. - -**Version:** Stage 9 (Post-Refactoring) -**Architecture:** Functional Core + Imperative Shell -**Test Focus:** Verify behavior parity + new architecture stability - ---- - -## Pre-Testing Setup - -### Environment Preparation -- [ ] Launch level: `TestLevel_Movement` (или basic test map) -- [ ] Place BP_MainCharacter with AC_Movement component -- [ ] Verify AC_DebugHUD component attached -- [ ] Enable ShowVisualDebug in debug HUD -- [ ] Set GameTimeScale = 1.0 (normal speed) - -### Debug HUD Verification -- [ ] Debug page "Movement Info" visible -- [ ] All fields updating in real-time: - - [ ] Current Velocity - - [ ] Speed - - [ ] Is Grounded - - [ ] Surface Type - - [ ] Movement State - - [ ] Rotation data - - [ ] Collision Count - -### Component Initialization Check -- [ ] AC_Movement.IsInitialized = true (visible in debug HUD) -- [ ] CapsuleComponent reference set -- [ ] Config values displaying correctly -- [ ] AngleThresholds converted to radians - ---- - -## TEST SUITE 1: Basic Movement & Physics - -### Test 1.1: Idle State -**Objective:** Verify default state on spawn - -**Procedure:** -1. Spawn character -2. Do not provide any input -3. Observe for 5 seconds - -**Expected Results:** -- [ ] Movement State: `Idle` -- [ ] Velocity: (0, 0, 0) or near-zero -- [ ] Speed: 0.0 -- [ ] Is Grounded: `true` -- [ ] Character not moving -- [ ] No rotation occurring - -**Pass Criteria:** All checks pass ✅ - ---- - -### Test 1.2: Forward Movement (Acceleration) -**Objective:** Verify smooth acceleration from standstill - -**Procedure:** -1. Start from Idle state -2. Press W (forward) fully -3. Observe velocity increase -4. Release W after 2 seconds - -**Expected Results:** -- [ ] Movement State changes: `Idle` → `Walking` -- [ ] Velocity.X increases smoothly (not instant) -- [ ] Speed reaches ~800 cm/s (MaxSpeed) -- [ ] Acceleration curve is smooth (VInterpTo behavior) -- [ ] Character rotates to face forward (Yaw → 0°) -- [ ] Visual debug shows green trace lines - -**Measurements:** -- Time to reach MaxSpeed: ~1-2 seconds -- Final Speed: 800 ± 50 cm/s - -**Pass Criteria:** Smooth acceleration, no jerking ✅ - ---- - -### Test 1.3: Friction (Deceleration) -**Objective:** Verify smooth deceleration when input released - -**Procedure:** -1. Reach MaxSpeed (from Test 1.2) -2. Release W (no input) -3. Observe velocity decrease -4. Wait until full stop - -**Expected Results:** -- [ ] Movement State changes: `Walking` → `Idle` -- [ ] Velocity decreases smoothly (VInterpTo) -- [ ] Speed → 0.0 -- [ ] Character continues sliding briefly (natural friction) -- [ ] Final stop is smooth, not abrupt - -**Measurements:** -- Stopping time: ~1-1.5 seconds (depends on Friction = 8.0) -- Stopping distance: ~600-800 cm - -**Pass Criteria:** Smooth deceleration, natural feel ✅ - ---- - -### Test 1.4: Directional Movement (WASD) -**Objective:** Verify all cardinal directions - -**Procedure:** -1. Test each direction individually: - - W (forward, X+) - - S (backward, X-) - - A (left, Y-) - - D (right, Y+) -2. For each direction: - - Accelerate to MaxSpeed - - Verify rotation - - Verify velocity vector - -**Expected Results:** - -| Input | Target Yaw | Velocity Direction | Pass | -|-------|------------|-------------------|------| -| W | 0° | (1, 0, 0) | [ ] | -| S | 180° | (-1, 0, 0) | [ ] | -| A | -90° | (0, -1, 0) | [ ] | -| D | 90° | (0, 1, 0) | [ ] | - -- [ ] All directions reach MaxSpeed -- [ ] Rotation targets correct yaw -- [ ] Smooth transitions between directions - -**Pass Criteria:** All 4 directions work correctly ✅ - ---- - -### Test 1.5: Diagonal Movement -**Objective:** Verify combined input vectors - -**Procedure:** -1. Press W+D simultaneously (northeast) -2. Observe velocity and rotation -3. Repeat for all diagonals: - - W+D (northeast) - - W+A (northwest) - - S+D (southeast) - - S+A (southwest) - -**Expected Results:** -- [ ] Velocity vector is normalized diagonal -- [ ] Speed still reaches MaxSpeed (not faster on diagonals) -- [ ] Rotation faces correct diagonal direction -- [ ] Smooth movement, no stuttering - -**Diagonal Angles:** -- W+D: 45° -- W+A: -45° -- S+D: 135° -- S+A: -135° - -**Pass Criteria:** Diagonals work, no speed advantage ✅ - ---- - -### Test 1.6: Rotation Speed -**Objective:** Verify character rotates at correct speed - -**Procedure:** -1. Face forward (W) -2. Switch to backward (S) input -3. Measure rotation time -4. Verify rotation speed - -**Expected Results:** -- [ ] Rotation Delta decreases smoothly -- [ ] Is Rotating: `true` during rotation -- [ ] Rotation Speed: 360°/sec (default config) -- [ ] 180° rotation takes ~0.5 seconds -- [ ] Character doesn't snap, rotates smoothly - -**Measurements:** -- Start Yaw: 0° -- End Yaw: 180° -- Rotation Time: 0.5 seconds ± 0.1s -- Calculated Speed: ~360°/sec - -**Pass Criteria:** Rotation smooth and matches config ✅ - ---- - -### Test 1.7: Min Speed For Rotation -**Objective:** Verify rotation threshold (MinSpeedForRotation) - -**Procedure:** -1. Set MinSpeedForRotation = 50.0 in Config -2. Apply very light input (analog stick barely pressed) -3. Observe speed and rotation - -**Expected Results:** -- [ ] Speed < 50 cm/s: Is Rotating = `false` -- [ ] Speed > 50 cm/s: Is Rotating = `true` -- [ ] Character doesn't jitter when nearly stopped -- [ ] Clean threshold behavior - -**Pass Criteria:** Threshold prevents rotation jitter ✅ - ---- - -## TEST SUITE 2: Ground Detection & Snapping - -### Test 2.1: Ground Detection (Flat Surface) -**Objective:** Verify basic ground detection - -**Procedure:** -1. Stand on flat ground -2. Observe debug info -3. Verify ground trace (visual debug) - -**Expected Results:** -- [ ] Is Grounded: `true` -- [ ] Surface Type: `Walkable` -- [ ] Ground Hit has valid BlockingHit -- [ ] Ground trace line visible (downward from capsule) -- [ ] Trace distance: GroundTraceDistance (50 cm default) - -**Pass Criteria:** Ground detected correctly ✅ - ---- - -### Test 2.2: Ground Snapping -**Objective:** Verify character stays on ground (no floating) - -**Procedure:** -1. Walk across slightly uneven terrain -2. Walk down small slopes (< 10°) -3. Observe Z position - -**Expected Results:** -- [ ] Character stays on ground (no floating) -- [ ] Z position updates to follow terrain -- [ ] No visible jitter or bouncing -- [ ] Smooth transition over bumps - -**Visual Check:** -- No air gap between capsule and ground -- Feet always touching surface (if model has feet) - -**Pass Criteria:** Perfect ground contact ✅ - ---- - -### Test 2.3: Airborne State -**Objective:** Verify loss of ground contact - -**Procedure:** -1. Walk character off platform edge -2. Observe state transition -3. Monitor gravity application - -**Expected Results:** -- [ ] Movement State: `Walking` → `Airborne` -- [ ] Is Grounded: `true` → `false` -- [ ] Surface Type: `Walkable` → `None` -- [ ] Gravity starts applying (Velocity.Z decreasing) -- [ ] Ground trace shows no hit - -**Gravity Check:** -- [ ] Velocity.Z decreases by ~980 cm/s² (Gravity) -- [ ] Character falls realistically - -**Pass Criteria:** Proper air state, gravity works ✅ - ---- - -### Test 2.4: Landing -**Objective:** Verify return to ground state - -**Procedure:** -1. Jump/fall from elevated position -2. Land on flat ground -3. Observe state transition - -**Expected Results:** -- [ ] Movement State: `Airborne` → `Idle` or `Walking` -- [ ] Is Grounded: `false` → `true` -- [ ] Velocity.Z: negative → 0 (upon landing) -- [ ] Smooth landing, no bounce -- [ ] Ground snapping activates - -**Pass Criteria:** Clean landing transition ✅ - ---- - -## TEST SUITE 3: Surface Classification - -### Test 3.1: Walkable Surface (≤50°) -**Objective:** Verify walkable angle threshold - -**Test Setup:** -1. Create ramp at 30° angle -2. Create ramp at 50° angle (boundary) - -**Procedure:** -1. Walk up 30° ramp -2. Walk up 50° ramp -3. Observe Surface Type - -**Expected Results:** -- [ ] 30° ramp: Surface Type = `Walkable` -- [ ] 50° ramp: Surface Type = `Walkable` (boundary) -- [ ] Normal movement possible on both -- [ ] Character doesn't slide - -**Pass Criteria:** Walkable threshold correct ✅ - ---- - -### Test 3.2: Steep Slope (50°-85°) -**Objective:** Verify steep slope detection - -**Test Setup:** -1. Create ramp at 60° angle -2. Create ramp at 80° angle - -**Procedure:** -1. Walk onto 60° slope -2. Observe Surface Type change -3. Test on 80° slope - -**Expected Results:** -- [ ] Surface Type = `SteepSlope` -- [ ] Movement State = `Sliding` -- [ ] Character starts sliding (future feature: will implement physics) -- [ ] Input may not prevent sliding - -**Note:** Sliding physics not yet implemented (Stage 11+), but classification should work. - -**Pass Criteria:** Classification correct ✅ - ---- - -### Test 3.3: Wall (85°-95°) -**Objective:** Verify wall detection - -**Test Setup:** -1. Walk into vertical wall (90° angle) - -**Procedure:** -1. Walk directly into wall -2. Observe collision response -3. Check Surface Type - -**Expected Results:** -- [ ] Surface Type = `Wall` -- [ ] Movement State = `Blocked` -- [ ] Character stops at wall -- [ ] Collision sweep detects hit -- [ ] Is Blocked: `true` - -**Pass Criteria:** Wall blocks movement ✅ - ---- - -### Test 3.4: Ceiling (>95°) -**Objective:** Verify ceiling detection - -**Test Setup:** -1. Walk under low ceiling -2. Create overhang at >95° angle - -**Procedure:** -1. Walk under ceiling geometry -2. Observe Surface Type if hit - -**Expected Results:** -- [ ] Surface Type = `Ceiling` (if hit occurs) -- [ ] Character blocked from moving into ceiling -- [ ] Proper collision response - -**Note:** Ceiling detection may be rare in normal gameplay. - -**Pass Criteria:** Ceiling classified correctly ✅ - ---- - -## TEST SUITE 4: Collision System - -### Test 4.1: Wall Collision (Front) -**Objective:** Verify frontal collision detection - -**Procedure:** -1. Run directly into wall at MaxSpeed -2. Observe sweep collision -3. Check collision response - -**Expected Results:** -- [ ] Character stops at wall surface -- [ ] No tunneling through wall -- [ ] Is Blocked: `true` -- [ ] Collision Count: >0 (visible in debug) -- [ ] Visual debug shows red hit marker at impact point -- [ ] Character position: exactly at wall surface - -**Collision Accuracy:** -- [ ] No penetration into wall geometry -- [ ] Stop position consistent across tests - -**Pass Criteria:** Perfect collision stop ✅ - ---- - -### Test 4.2: Surface Sliding (Angled Wall) -**Objective:** Verify slide vector calculation - -**Procedure:** -1. Run into wall at 45° angle -2. Observe sliding behavior -3. Test various angles - -**Expected Results:** -- [ ] Character slides along wall surface -- [ ] Slide direction perpendicular to hit normal -- [ ] No stuck behavior -- [ ] Smooth sliding motion -- [ ] Speed maintained during slide - -**Test Angles:** -- 15° angle: Mostly forward, slight slide -- 45° angle: Equal forward + slide -- 75° angle: Mostly slide, little forward - -**Pass Criteria:** Smooth sliding on all angles ✅ - ---- - -### Test 4.3: Corner Navigation -**Objective:** Verify multi-collision handling - -**Procedure:** -1. Run into 90° corner (two walls meeting) -2. Attempt to move into corner -3. Observe collision response - -**Expected Results:** -- [ ] Character stops cleanly at corner -- [ ] No jittering or oscillation -- [ ] Collision Count reasonable ( 0.01 - - Speed > 1.0 cm/s -- [ ] Clean transition, no flicker - -**Pass Criteria:** Transition instant and clean ✅ - ---- - -### Test 5.2: Walking → Idle Transition -**Objective:** Verify state transition on input release - -**Procedure:** -1. Reach Walking state (moving) -2. Release all input -3. Observe state change - -**Expected Results:** -- [ ] Movement State: `Walking` → `Idle` -- [ ] Transition occurs when: - - Input Magnitude ≤ 0.01 - - Speed ≤ 1.0 cm/s (after friction) -- [ ] Brief delay due to friction deceleration - -**Pass Criteria:** Transition smooth after stop ✅ - ---- - -### Test 5.3: Walking → Airborne Transition -**Objective:** Verify state change when leaving ground - -**Procedure:** -1. Walk off platform edge -2. Observe immediate state change - -**Expected Results:** -- [ ] Movement State: `Walking` → `Airborne` -- [ ] Transition instant (same frame as IsGrounded = false) -- [ ] No intermediate states - -**Pass Criteria:** Instant transition ✅ - ---- - -### Test 5.4: Airborne → Walking Transition -**Objective:** Verify landing state change - -**Procedure:** -1. Fall/jump and land -2. Have forward input during landing - -**Expected Results:** -- [ ] Movement State: `Airborne` → `Walking` -- [ ] If input present: lands in Walking -- [ ] If no input: lands in Idle -- [ ] Speed preserved from air movement - -**Pass Criteria:** Landing state correct ✅ - ---- - -### Test 5.5: Walking → Blocked Transition -**Objective:** Verify blocked state on collision - -**Procedure:** -1. Run into wall -2. Observe state change - -**Expected Results:** -- [ ] Movement State: `Walking` → `Blocked` -- [ ] Occurs when: - - Sweep collision detected - - Is Blocked = true -- [ ] State returns to Walking when moving away from wall - -**Pass Criteria:** Blocked state triggers correctly ✅ - ---- - -### Test 5.6: Walking → Sliding Transition -**Objective:** Verify steep slope detection - -**Test Setup:** -1. Create steep ramp (60°-85°) - -**Procedure:** -1. Walk onto steep ramp -2. Observe state change - -**Expected Results:** -- [ ] Movement State: `Walking` → `Sliding` -- [ ] Surface Type = `SteepSlope` -- [ ] State persists while on steep surface - -**Note:** Sliding physics not implemented yet, but state should be set. - -**Pass Criteria:** State classification correct ✅ - ---- - -## TEST SUITE 6: Data Flow & Architecture - -### Test 6.1: State Immutability -**Objective:** Verify state is never mutated - -**Procedure:** -1. Add debug logging in AC_Movement.ProcessMovementInput() -2. Log CurrentMovementState before ProcessMovement() -3. Log CurrentMovementState after ProcessMovement() -4. Move character - -**Expected Results:** -- [ ] Old state object unchanged (log shows same values) -- [ ] New state object has different memory address -- [ ] State transformation is pure (same input → same output) - -**Technical Check:** -```typescript -// Before -console.log(this.CurrentMovementState.Location); -const oldState = this.CurrentMovementState; - -// Process -this.CurrentMovementState = BFL_MovementProcessor.ProcessMovement(...); - -// After -console.log(oldState.Location === this.CurrentMovementState.Location); // false -``` - -**Pass Criteria:** State never mutated ✅ - ---- - -### Test 6.2: Pipeline Phase Execution -**Objective:** Verify all 6 phases execute in order - -**Procedure:** -1. Add debug logging in BFL_MovementProcessor.ProcessMovement() -2. Log entry to each phase -3. Move character -4. Verify log output - -**Expected Phases (in order):** -- [ ] PHASE 1: Input & Rotation -- [ ] PHASE 2: Ground Detection -- [ ] PHASE 3: Physics Calculation -- [ ] PHASE 4: Movement Application (Sweep) -- [ ] PHASE 5: Ground Snapping -- [ ] PHASE 6: State Determination - -**Log Output Example:** -``` -[ProcessMovement] PHASE 1: Input & Rotation -[ProcessMovement] PHASE 2: Ground Detection -[ProcessMovement] PHASE 3: Physics Calculation -[ProcessMovement] PHASE 4: Movement Application -[ProcessMovement] PHASE 5: Ground Snapping -[ProcessMovement] PHASE 6: State Determination -[ProcessMovement] Return new state -``` - -**Pass Criteria:** All phases execute in correct order ✅ - ---- - -### Test 6.3: Subsystem Independence -**Objective:** Verify modules don't have hidden dependencies - -**Procedure:** -1. Test each BFL_* module in isolation (if possible via unit tests) -2. Verify no shared global state -3. Confirm pure function behavior - -**Modules to Test:** -- [ ] BFL_Kinematics (pure physics math) -- [ ] BFL_RotationController (pure rotation math) -- [ ] BFL_SurfaceClassifier (pure classification) -- [ ] BFL_MovementStateMachine (pure FSM logic) - -**Expected Results:** -- [ ] Each module works independently -- [ ] No unexpected side effects -- [ ] Same input always produces same output (determinism) - -**Pass Criteria:** Full module independence ✅ - ---- - -### Test 6.4: Configuration Isolation -**Objective:** Verify DA_MovementConfig is read-only - -**Procedure:** -1. Create two characters with different configs -2. Change MaxSpeed on Character A -3. Verify Character B unaffected - -**Expected Results:** -- [ ] Character A uses Config A -- [ ] Character B uses Config B -- [ ] No config bleeding between instances -- [ ] Each AC_Movement has own Config instance - -**Pass Criteria:** Config properly isolated ✅ - ---- - -## TEST SUITE 7: Debug & Visualization - -### Test 7.1: Debug HUD Updates -**Objective:** Verify all debug fields update in real-time - -**Procedure:** -1. Enable debug HUD -2. Perform various movements -3. Observe all fields updating - -**Fields to Verify:** -- [ ] Current Velocity (changes with movement) -- [ ] Speed (0-800 range) -- [ ] Is Grounded (true/false toggle) -- [ ] Surface Type (changes on different surfaces) -- [ ] Movement State (FSM state transitions) -- [ ] Input Magnitude (0-1 range) -- [ ] Current Yaw (rotation angle) -- [ ] Rotation Delta (decreases to 0) -- [ ] Is Rotating (true/false) -- [ ] Collision Checks (varies with speed) -- [ ] Sweep Blocked (true when hitting wall) - -**Update Frequency:** -- Should update every frame -- No stale data -- Smooth value transitions - -**Pass Criteria:** All fields live and accurate ✅ - ---- - -### Test 7.2: Visual Debug (Traces) -**Objective:** Verify debug trace rendering - -**Procedure:** -1. Enable ShowVisualDebug = true -2. Move character -3. Observe visual traces in world - -**Expected Traces:** -- [ ] Ground trace (downward line from capsule) - - Green if hit - - Red if no hit -- [ ] Swept collision traces (capsule path) - - Multiple traces showing steps - - Red markers at collision points -- [ ] Traces persist briefly (ForDuration mode) - -**Pass Criteria:** Visual debug clear and helpful ✅ - ---- - -### Test 7.3: Config Display -**Objective:** Verify config constants shown in debug - -**Procedure:** -1. Open debug HUD -2. Verify all config values visible - -**Constants to Check:** -- [ ] Max Speed: 800.0 -- [ ] Acceleration: 10.0 -- [ ] Friction: 8.0 -- [ ] Gravity: 980.0 -- [ ] Rotation Speed: 360.0 -- [ ] Min Speed For Rotation: 50.0 -- [ ] Ground Trace Distance: 50.0 - -**Pass Criteria:** All config values accurate ✅ - ---- - -## TEST SUITE 8: Performance - -### Test 8.1: Frame Time -**Objective:** Verify movement processing stays under budget - -**Procedure:** -1. Enable UE profiler (stat game) -2. Move character continuously -3. Observe AC_Movement tick time - -**Expected Results:** -- [ ] Average frame time: 0.3-0.7ms -- [ ] Max frame time: <1.5ms -- [ ] No spikes above 2ms -- [ ] Consistent performance - -**Budget:** <1ms target for 60 FPS - -**Pass Criteria:** Performance within budget ✅ - ---- - -### Test 8.2: Collision Check Count -**Objective:** Verify sweep efficiency - -**Procedure:** -1. Move at various speeds -2. Monitor Collision Count in debug HUD -3. Test complex geometry - -**Expected Results:** -- [ ] Low speed: 1-3 checks -- [ ] Medium speed: 3-8 checks -- [ ] High speed: 8-15 checks -- [ ] Complex geometry: <20 checks -- [ ] Never hits MaxCollisionChecks (25) in normal gameplay - -**Pass Criteria:** Checks reasonable for speed ✅ - ---- - -### Test 8.3: Ground Trace Frequency -**Objective:** Verify ground detection cost - -**Procedure:** -1. Profile LineTraceByChannel calls -2. Verify one trace per frame - -**Expected Results:** -- [ ] Exactly 1 ground trace per frame -- [ ] Trace executes even when airborne (for landing detection) -- [ ] Trace cost: <0.1ms - -**Pass Criteria:** Trace frequency optimal ✅ - ---- - -## TEST SUITE 9: Edge Cases - -### Test 9.1: Initialization Check -**Objective:** Verify system handles uninitialized state - -**Procedure:** -1. Create AC_Movement without calling InitializeMovementSystem() -2. Call ProcessMovementInput() -3. Verify graceful handling - -**Expected Results:** -- [ ] IsInitialized = false -- [ ] ProcessMovementInput() returns early (no processing) -- [ ] No crash or error -- [ ] Character doesn't move - -**Pass Criteria:** Graceful handling of no-init ✅ - ---- - -### Test 9.2: Null CapsuleComponent -**Objective:** Verify handling of missing capsule - -**Procedure:** -1. Initialize with CapsuleComponent = null -2. Attempt movement -3. Verify system behavior - -**Expected Results:** -- [ ] No crash -- [ ] Collision detection skips (no traces) -- [ ] Character may move without collision -- [ ] Ground detection returns empty HitResult - -**Pass Criteria:** No crash, graceful degradation ✅ - ---- - -### Test 9.3: Zero DeltaTime -**Objective:** Verify handling of edge case time - -**Procedure:** -1. Pass DeltaTime = 0.0 to ProcessMovementInput() -2. Observe behavior - -**Expected Results:** -- [ ] No division by zero errors -- [ ] No NaN values in velocity -- [ ] State remains stable -- [ ] No movement applied - -**Pass Criteria:** Zero deltatime handled ✅ - ---- - -### Test 9.4: Extreme Velocity -**Objective:** Verify handling of very high speeds - -**Procedure:** -1. Temporarily set MaxSpeed = 5000 cm/s -2. Reach max speed -3. Observe collision system - -**Expected Results:** -- [ ] Swept collision still works -- [ ] No tunneling -- [ ] Collision Count increases (more steps needed) -- [ ] May hit MaxCollisionChecks, but no crash -- [ ] Performance acceptable - -**Pass Criteria:** System stable at high speeds ✅ - ---- - -### Test 9.5: Rapid Direction Changes -**Objective:** Verify stability with chaotic input - -**Procedure:** -1. Rapidly alternate between W and S (forward/back) -2. Spin mouse rapidly while moving -3. Mash all WASD keys randomly - -**Expected Results:** -- [ ] No crashes or errors -- [ ] Velocity responds to input changes -- [ ] Rotation updates smoothly -- [ ] No state machine flicker -- [ ] Character remains stable - -**Pass Criteria:** Stable under chaos ✅ - ---- - -## TEST SUITE 10: Regression Tests - -### Test 10.1: Behavior Parity with Pre-Refactor -**Objective:** Verify refactoring preserved behavior - -**Setup:** -1. Record video of movement BEFORE refactor -2. Record video of movement AFTER refactor -3. Compare side-by-side - -**Scenarios to Compare:** -- [ ] Acceleration from idle -- [ ] Deceleration to stop -- [ ] Rotation speed -- [ ] Wall collision -- [ ] Surface sliding -- [ ] Ground detection - -**Expected Results:** -- [ ] Identical acceleration curves -- [ ] Same rotation speed -- [ ] Identical collision response -- [ ] No behavioral regressions - -**Pass Criteria:** Behavior 100% identical ✅ - ---- - -### Test 10.2: Config Compatibility -**Objective:** Verify DA_MovementConfig unchanged - -**Procedure:** -1. Load old config asset (pre-refactor) -2. Apply to new AC_Movement -3. Verify all values apply correctly - -**Expected Results:** -- [ ] All config values load correctly -- [ ] No missing or new required fields -- [ ] Behavior matches config settings -- [ ] No migration needed - -**Pass Criteria:** Full config backward compatibility ✅ - ---- - -## TEST SUITE 11: Integration Tests - -### Test 11.1: BP_MainCharacter Integration -**Objective:** Verify AC_Movement works in character context - -**Procedure:** -1. Place BP_MainCharacter in level -2. Play as character -3. Test full movement suite - -**Expected Results:** -- [ ] All movement tests pass in character context -- [ ] Input processing works correctly -- [ ] Camera-relative movement correct -- [ ] Animation system receives correct state (if implemented) - -**Pass Criteria:** Full character integration ✅ - ---- - -### Test 11.2: Multiple Characters -**Objective:** Verify no state bleeding between instances - -**Procedure:** -1. Spawn 3 BP_MainCharacters -2. Move each independently -3. Verify no interference - -**Expected Results:** -- [ ] Each character has independent state -- [ ] No shared global state -- [ ] Collision between characters works -- [ ] Each character responds to own input - -**Pass Criteria:** Full instance isolation ✅ - ---- - -## Testing Summary - -### Critical Tests (Must Pass) -1. ✅ Basic Movement (1.1-1.7) -2. ✅ Ground Detection (2.1-2.4) -3. ✅ Wall Collision (4.1) -4. ✅ State Immutability (6.1) -5. ✅ Behavior Parity (10.1) - -### Important Tests (Should Pass) -1. Surface Classification (3.1-3.4) -2. Surface Sliding (4.2) -3. State Machine (5.1-5.6) -4. Debug Visualization (7.1-7.3) - -### Performance Tests (Target) -1. Frame Time <1ms (8.1) -2. Collision Checks Reasonable (8.2) - -### Edge Case Tests (Nice to Have) -1. Null handling (9.1-9.2) -2. Extreme conditions (9.3-9.5) - ---- - -## Test Results Template - -### Test Session Info -- **Date:** ___________ -- **Tester:** ___________ -- **Build:** ___________ -- **Level:** ___________ - -### Summary -- **Tests Run:** ___ / 60 -- **Tests Passed:** ___ / ___ -- **Tests Failed:** ___ / ___ -- **Critical Failures:** ___ - -### Failed Tests -1. Test X.X: [Description] - - **Expected:** [...] - - **Actual:** [...] - - **Severity:** Critical / High / Medium / Low - -### Performance Metrics -- **Average Frame Time:** ___ms -- **Max Frame Time:** ___ms -- **Avg Collision Checks:** ___ -- **Max Collision Checks:** ___ - -### Notes -[Any additional observations or issues] - ---- - -## Sign-Off - -### Approval Criteria -- [ ] All Critical Tests passed -- [ ] No Critical or High severity failures -- [ ] Performance within budget -- [ ] Behavior matches specification -- [ ] No regressions detected - -### Approved By -- **Developer:** ___________ Date: ___________ -- **QA:** ___________ Date: ___________ - ---- - -## Conclusion - -This comprehensive manual testing checklist ensures the refactored Movement System maintains behavior parity while validating the new architecture. Focus on Critical Tests first, then expand to full coverage. - -**Estimated Testing Time:** 2-3 hours for full suite - -**Recommended Approach:** -1. Day 1: Critical Tests (Suite 1-2, 6.1, 10.1) -2. Day 2: Important Tests (Suite 3-5, 7) -3. Day 3: Performance & Edge Cases (Suite 8-9) - -Good luck testing! 🎮 diff --git a/Content/Movement/Physics/BFL_Kinematics.ts b/Content/Movement/Physics/BFL_Kinematics.ts deleted file mode 100644 index 0385b12..0000000 --- a/Content/Movement/Physics/BFL_Kinematics.ts +++ /dev/null @@ -1,179 +0,0 @@ -// Movement/Physics/BFL_Kinematics.ts - -import type { DA_MovementConfig } from '#root/Movement/Core/DA_MovementConfig.ts'; -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import type { Float } from '#root/UE/Float.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -class BFL_KinematicsClass extends BlueprintFunctionLibrary { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GROUND MOVEMENT - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Calculate new velocity for ground-based movement with acceleration - * Uses VInterpTo for smooth acceleration towards target velocity - * Only affects horizontal (XY) components, preserves vertical (Z) - * - * @param CurrentVelocity - Current character velocity (cm/s) - * @param InputVector - Normalized input direction from player/AI - * @param DeltaTime - Frame delta time for frame-rate independence (s) - * @param Config - Movement configuration with MaxSpeed and Acceleration - * @returns New velocity vector with updated horizontal components - * - * @example - * // Character moving forward with input (1, 0, 0) - * const newVel = Kinematics.CalculateGroundVelocity( - * new Vector(400, 0, 0), // Current velocity - * new Vector(1, 0, 0), // Forward input - * 0.016, // 60 FPS delta - * config - * ); - * // Returns: Vector(450, 0, 0) - accelerated towards MaxSpeed - * - * @pure true - * @category Ground Movement - */ - public CalculateGroundVelocity( - CurrentVelocity: Vector, - InputVector: Vector, - DeltaTime: Float, - Config: DA_MovementConfig - ): Vector { - if (MathLibrary.VectorLength(InputVector) > 0.01) { - const CalculateTargetVelocity = ( - inputVector: Vector, - maxSpeed: Float - ): Vector => - new Vector( - MathLibrary.Normal(inputVector).X * maxSpeed, - MathLibrary.Normal(inputVector).Y * maxSpeed, - MathLibrary.Normal(inputVector).Z * maxSpeed - ); - - return MathLibrary.VInterpTo( - new Vector(CurrentVelocity.X, CurrentVelocity.Y, 0), - CalculateTargetVelocity(InputVector, Config.MaxSpeed), - DeltaTime, - Config.Acceleration - ); - } else { - return this.CalculateFriction(CurrentVelocity, DeltaTime, Config); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // FRICTION - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Apply friction to horizontal velocity (deceleration when no input) - * Smoothly interpolates velocity towards zero using friction rate - * Only affects horizontal (XY) components, preserves vertical (Z) - * - * @param CurrentVelocity - Current character velocity (cm/s) - * @param DeltaTime - Frame delta time (s) - * @param Config - Movement configuration with Friction rate - * @returns New velocity vector with friction applied to horizontal components - * - * @example - * // Character sliding to stop after input released - * const newVel = Kinematics.ApplyFriction( - * new Vector(500, 0, 0), // Moving forward - * 0.016, // 60 FPS delta - * config // Friction = 8.0 - * ); - * // Returns: Vector(450, 0, 0) - smoothly decelerating - * - * @pure true - * @category Friction - */ - public CalculateFriction( - CurrentVelocity: Vector, - DeltaTime: Float, - Config: DA_MovementConfig - ): Vector { - return MathLibrary.VInterpTo( - new Vector(CurrentVelocity.X, CurrentVelocity.Y, 0), - new Vector(0, 0, CurrentVelocity.Z), - DeltaTime, - Config.Friction - ); - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAVITY - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Apply gravity to vertical velocity when airborne - * Only affects Z component, horizontal velocity unchanged - * Gravity is NOT applied when grounded (Z velocity set to 0) - * - * @param CurrentVelocity - Current character velocity (cm/s) - * @param IsGrounded - Whether character is on walkable surface - * @param Config - Movement configuration with Gravity force - * @returns New velocity vector with gravity applied to vertical component - * - * @example - * // Character falling (not grounded) - * const newVel = Kinematics.ApplyGravity( - * new Vector(500, 0, -200), // Moving forward and falling - * false, // Not grounded - * config // Gravity = 980 cm/s² - * ); - * // Returns: Vector(500, 0, -216.8) - falling faster - * - * @example - * // Character on ground - * const newVel = Kinematics.ApplyGravity( - * new Vector(500, 0, -10), // Small downward velocity - * true, // Grounded - * config - * ); - * // Returns: Vector(500, 0, 0) - vertical velocity zeroed - * - * @pure true - * @category Gravity - */ - public CalculateGravity( - CurrentVelocity: Vector, - IsGrounded: boolean, - Config: DA_MovementConfig - ): Vector { - if (!IsGrounded) { - return new Vector( - CurrentVelocity.X, - CurrentVelocity.Y, - CurrentVelocity.Z - Config.Gravity - ); - } else { - return new Vector(CurrentVelocity.X, CurrentVelocity.Y, 0); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VELOCITY QUERIES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Get horizontal speed (magnitude of XY velocity) - * Ignores vertical component, useful for animation and debug display - * - * @param Velocity - Velocity vector to measure - * @returns Speed in cm/s (horizontal plane only) - * - * @example - * const speed = Kinematics.GetHorizontalSpeed(new Vector(300, 400, -100)); - * // Returns: 500.0 (sqrt(300² + 400²)) - * - * @pure true - * @category Velocity Queries - */ - public GetHorizontalSpeed(Velocity: Vector): Float { - return MathLibrary.VectorLength(new Vector(Velocity.X, Velocity.Y, 0)); - } -} - -export const BFL_Kinematics = new BFL_KinematicsClass(); diff --git a/Content/Movement/Physics/BFL_Kinematics.uasset b/Content/Movement/Physics/BFL_Kinematics.uasset deleted file mode 100644 index e3b78f9..0000000 --- a/Content/Movement/Physics/BFL_Kinematics.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c888760d1f704f56d97ebe9bd45a13c16311eae6a0c891f21f14181148ddf9d -size 140329 diff --git a/Content/Movement/Rotation/BFL_RotationController.ts b/Content/Movement/Rotation/BFL_RotationController.ts deleted file mode 100644 index 15d6c96..0000000 --- a/Content/Movement/Rotation/BFL_RotationController.ts +++ /dev/null @@ -1,256 +0,0 @@ -// Movement/Rotation/BFL_RotationController.ts - -import type { DA_MovementConfig } from '#root/Movement/Core/DA_MovementConfig.ts'; -import type { S_RotationResult } from '#root/Movement/Rotation/S_RotationResult.ts'; -import type { Float } from '#root/UE/Float.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { Rotator } from '#root/UE/Rotator.ts'; -import type { Vector } from '#root/UE/Vector.ts'; - -/** - * Character Rotation Controller - * - * Pure functional module for character rotation calculations - * Handles smooth rotation toward movement direction - * All methods are deterministic and side-effect free - * - * @category Movement Rotation - * @pure All methods are pure functions - */ -class BFL_RotationControllerClass { - // ════════════════════════════════════════════════════════════════════════════════════════ - // TARGET CALCULATION - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Calculate target yaw angle from movement direction - * Converts 2D movement vector to rotation angle - * - * @param MovementDirection - Movement direction vector (XY plane) - * @returns Target yaw angle in degrees - * - * @example - * // Moving forward (X+) - * const yaw = RotationController.CalculateTargetYaw(new Vector(1, 0, 0)); - * // Returns: 0° - * - * @example - * // Moving right (Y+) - * const yaw = RotationController.CalculateTargetYaw(new Vector(0, 1, 0)); - * // Returns: 90° - * - * @pure true - * @category Target Calculation - */ - public CalculateTargetYaw(MovementDirection: Vector): Float { - // Use atan2 to get angle from X/Y components - // Returns angle in degrees - return MathLibrary.Atan2Degrees(MovementDirection.Y, MovementDirection.X); - } - - /** - * Calculate target rotation from movement direction - * Creates full Rotator with only yaw set (pitch/roll = 0) - * - * @param MovementDirection - Movement direction vector - * @returns Target rotation (yaw only, pitch/roll = 0) - * - * @pure true - * @category Target Calculation - */ - public CalculateTargetRotation(MovementDirection: Vector): Rotator { - return new Rotator(0, this.CalculateTargetYaw(MovementDirection), 0); - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // ROTATION INTERPOLATION - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Interpolate rotation smoothly toward target - * Handles angle wraparound (180°/-180° boundary) - * - * @param CurrentRotation - Current character rotation - * @param TargetRotation - Desired target rotation - * @param RotationSpeed - Rotation speed in degrees/sec - * @param DeltaTime - Frame delta time - * @param MinSpeedForRotation - Minimum speed to allow rotation (default: 0) - * @param CurrentSpeed - Current movement speed for threshold check - * @returns RotationResult with new rotation and metadata - * - * @example - * const result = RotationController.InterpolateRotation( - * new Rotator(0, 0, 0), // Current: facing forward - * new Rotator(0, 90, 0), // Target: facing right - * 720, // 720°/sec rotation speed - * 0.016, // 60 FPS delta - * 50, // Min speed threshold - * 500 // Current speed - * ); - * // Returns: Rotator smoothly interpolated toward 90° - * - * @pure true - * @category Rotation Interpolation - */ - public InterpolateRotation( - CurrentRotation: Rotator, - TargetRotation: Rotator, - RotationSpeed: Float, - DeltaTime: Float, - MinSpeedForRotation: Float = 0.0, - CurrentSpeed: Float = 0.0 - ): S_RotationResult { - // Check if character is moving fast enough to rotate - if (CurrentSpeed >= MinSpeedForRotation) { - // Calculate angular distance with wraparound handling - const angularDistance = this.GetAngularDistance( - CurrentRotation.yaw, - TargetRotation.yaw - ); - - // Check if rotation is not complete (within 1° tolerance) - if (MathLibrary.abs(angularDistance) <= 1.0) { - const CalculateNewYaw = ( - currentRotationYaw: Float, - rotationDirection: Integer, - rotationSpeed: Float, - deltaTime: Float - ): Float => - currentRotationYaw + - MathLibrary.Min( - rotationSpeed * deltaTime, - MathLibrary.abs(angularDistance) - ) * - rotationDirection; - - return { - Rotation: new Rotator( - 0, - CalculateNewYaw( - CurrentRotation.yaw, - angularDistance > 0 ? -1 : 1, - RotationSpeed, - DeltaTime - ), - 0 - ), - IsRotating: true, - RemainingDelta: MathLibrary.abs(angularDistance), - }; - } else { - return { - Rotation: TargetRotation, - IsRotating: false, - RemainingDelta: 0.0, - }; - } - } else { - return { - Rotation: CurrentRotation, - IsRotating: false, - RemainingDelta: 0.0, - }; - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // ANGLE UTILITIES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Calculate the shortest angular distance between two angles - * Handles wraparound for shortest path - * - * @param fromAngle - Starting angle in degrees - * @param toAngle - Target angle in degrees - * @returns Signed angular distance (positive = clockwise, negative = counter-clockwise) - * - * @example - * GetAngularDistance(10, 350) // Returns: -20 (shorter to go counter-clockwise) - * GetAngularDistance(350, 10) // Returns: 20 (shorter to go clockwise) - * GetAngularDistance(0, 180) // Returns: 180 (either direction same) - * - * @pure true - * @category Angle Utilities - */ - public GetAngularDistance(fromAngle: Float, toAngle: Float): Float { - // Calculate raw difference - let difference = fromAngle - toAngle; - - // Normalize to the shortest path - if (difference > 180) { - difference -= 360; - } else if (difference < -180) { - difference += 360; - } - - return difference; - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // CONVENIENCE METHODS - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Update character rotation toward movement direction - * Convenience method combining target calculation and interpolation - * - * @param CurrentRotation - Current character rotation - * @param MovementDirection - Movement direction vector - * @param Config - Movement configuration with rotation settings - * @param DeltaTime - Frame delta time - * @param CurrentSpeed - Current movement speed - * @returns RotationResult with updated rotation - * - * @example - * const result = RotationController.UpdateRotation( - * CurrentRotation, - * InputVector, - * Config, - * DeltaTime, - * CurrentSpeed - * ); - * character.SetActorRotation(result.Rotation); - * - * @pure true - * @category Convenience Methods - */ - public UpdateRotation( - CurrentRotation: Rotator, - MovementDirection: Vector, - Config: DA_MovementConfig, - DeltaTime: Float, - CurrentSpeed: Float - ): S_RotationResult { - // Rotation if enabled in config - if (Config.ShouldRotateToMovement) { - // Rotation if movement - if (MathLibrary.VectorLength(MovementDirection) >= 0.01) { - // Calculate target and interpolate; - return this.InterpolateRotation( - CurrentRotation, - this.CalculateTargetRotation(MovementDirection), - Config.RotationSpeed, - DeltaTime, - Config.MinSpeedForRotation, - CurrentSpeed - ); - } else { - return { - Rotation: CurrentRotation, - IsRotating: false, - RemainingDelta: 0.0, - }; - } - } else { - return { - Rotation: CurrentRotation, - IsRotating: false, - RemainingDelta: 0.0, - }; - } - } -} - -export const BFL_RotationController = new BFL_RotationControllerClass(); diff --git a/Content/Movement/Rotation/BFL_RotationController.uasset b/Content/Movement/Rotation/BFL_RotationController.uasset deleted file mode 100644 index ea6ab4c..0000000 --- a/Content/Movement/Rotation/BFL_RotationController.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:88722b18fb5a0bafc4708884e916fb8c15b01217df68fed6d67fe6d75c52313d -size 219597 diff --git a/Content/Movement/Rotation/S_RotationResult.ts b/Content/Movement/Rotation/S_RotationResult.ts deleted file mode 100644 index a6365c2..0000000 --- a/Content/Movement/Rotation/S_RotationResult.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Movement/Rotation/S_RotationResult.ts - -import type { Float } from '#root/UE/Float.ts'; -import type { Rotator } from '#root/UE/Rotator.ts'; - -/** - * Rotation result data - * Contains updated rotation and metadata about rotation state - * - * @category Movement Rotation - */ -export interface S_RotationResult { - /** - * New rotation after interpolation - */ - Rotation: Rotator; - - /** - * Whether character is actively rotating - * False if rotation is complete or speed too low - */ - IsRotating: boolean; - - /** - * Angular distance remaining to target (degrees) - * Used for animations and debug - */ - RemainingDelta: Float; -} diff --git a/Content/Movement/Rotation/S_RotationResult.uasset b/Content/Movement/Rotation/S_RotationResult.uasset deleted file mode 100644 index 6e1b470..0000000 --- a/Content/Movement/Rotation/S_RotationResult.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dde72be48a9bc554d646480121fc642d1507d96977c220ed4c074439e7364710 -size 6125 diff --git a/Content/Movement/State/BFL_MovementStateMachine.ts b/Content/Movement/State/BFL_MovementStateMachine.ts deleted file mode 100644 index 0eb009c..0000000 --- a/Content/Movement/State/BFL_MovementStateMachine.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Movement/State/BFL_MovementStateMachine.ts - -import { E_MovementState } from '#root/Movement/Core/E_MovementState.ts'; -import type { S_MovementContext } from '#root/Movement/State/S_MovementContext.ts'; -import { E_SurfaceType } from '#root/Movement/Surface/E_SurfaceType.ts'; - -/** - * Movement State Machine - * - * Pure functional FSM for determining movement state - * Takes movement context and returns appropriate state - * No side effects - completely deterministic - * - * @category Movement State - * @pure All methods are pure functions - */ -class BFL_MovementStateMachineClass { - // ════════════════════════════════════════════════════════════════════════════════════════ - // STATE DETERMINATION - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Determine movement state based on current Context - * Main entry point for state machine logic - * - * @param Context - Current movement context - * @returns Appropriate movement state - * - * @example - * const state = MovementStateMachine.DetermineState({ - * IsGrounded: true, - * SurfaceType: E_SurfaceType.Walkable, - * InputMagnitude: 0.8, - * CurrentSpeed: 500, - * VerticalVelocity: 0, - * IsBlocked: false - * }); - * // Returns: E_MovementState.Walking - * - * @pure true - * @category State Determination - */ - public DetermineState(Context: S_MovementContext): E_MovementState { - // Priority 1: Check if grounded - if (Context.IsGrounded) { - // Priority 2: Check surface type - if (Context.SurfaceType === E_SurfaceType.SteepSlope) { - return E_MovementState.Sliding; - } else if ( - Context.SurfaceType === E_SurfaceType.Wall || - Context.SurfaceType === E_SurfaceType.Ceiling || - // Priority 3: Check if blocked by collision - Context.IsBlocked - ) { - return E_MovementState.Blocked; - } else { - // Priority 4: Determine ground state based on input - return this.DetermineGroundedState(Context); - } - } else { - return this.DetermineAirborneState(Context); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // STATE HELPERS - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Determine state when character is airborne - * Distinguishes between jumping, falling, etc. - * - * @param Context - Current movement context - * @returns Airborne-specific state - * - * @pure true - * @category State Helpers - */ - private DetermineAirborneState(Context: S_MovementContext): E_MovementState { - // Could extend this to differentiate Jump vs Fall - // For now, just return Airborne - return E_MovementState.Airborne; - } - - /** - * Determine state when character is on ground - * Distinguishes between idle, walking, running, etc. - * - * @param Context - Current movement context - * @returns Grounded-specific state - * - * @pure true - * @category State Helpers - */ - private DetermineGroundedState(Context: S_MovementContext): E_MovementState { - // Check if player is providing input - if (Context.InputMagnitude > 0.01 && Context.CurrentSpeed > 1.0) { - return E_MovementState.Walking; - } else { - return E_MovementState.Idle; - } - } -} - -export const BFL_MovementStateMachine = new BFL_MovementStateMachineClass(); diff --git a/Content/Movement/State/BFL_MovementStateMachine.uasset b/Content/Movement/State/BFL_MovementStateMachine.uasset deleted file mode 100644 index a6930fa..0000000 --- a/Content/Movement/State/BFL_MovementStateMachine.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a307994ae6463e73039b23d26933e72e958d95f0e4f2f8f037c8513fbb635ad7 -size 96267 diff --git a/Content/Movement/State/S_MovementContext.ts b/Content/Movement/State/S_MovementContext.ts deleted file mode 100644 index 21cedfb..0000000 --- a/Content/Movement/State/S_MovementContext.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Movement/State/S_MovementContext.ts - -import type { E_SurfaceType } from '#root/Movement/Surface/E_SurfaceType.ts'; -import type { Float } from '#root/UE/Float.ts'; - -/** - * Movement context data for state determination - * Contains all information needed to determine movement state - * - * @category Movement State - */ -export interface S_MovementContext { - /** - * Whether character is on walkable ground - */ - IsGrounded: boolean; - - /** - * Type of surface character is on - */ - SurfaceType: E_SurfaceType; - - /** - * Magnitude of player input (0-1) - */ - InputMagnitude: Float; - - /** - * Current horizontal movement speed (cm/s) - */ - CurrentSpeed: Float; - - /** - * Current vertical velocity (cm/s) - * Positive = moving up, Negative = falling - */ - VerticalVelocity: Float; - - /** - * Whether character is blocked by collision - */ - IsBlocked: boolean; -} diff --git a/Content/Movement/State/S_MovementContext.uasset b/Content/Movement/State/S_MovementContext.uasset deleted file mode 100644 index aa70820..0000000 --- a/Content/Movement/State/S_MovementContext.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e04ddc41d169b3b32a3e8e2eee516c849c665991455707874ccab65e57787499 -size 9217 diff --git a/Content/Movement/Surface/BFL_SurfaceClassifier.ts b/Content/Movement/Surface/BFL_SurfaceClassifier.ts deleted file mode 100644 index c1efff2..0000000 --- a/Content/Movement/Surface/BFL_SurfaceClassifier.ts +++ /dev/null @@ -1,123 +0,0 @@ -// Movement/Surface/BFL_SurfaceClassifier.ts - -import { BFL_Vectors } from '#root/Math/Libraries/BFL_Vectors.ts'; -import { E_SurfaceType } from '#root/Movement/Surface/E_SurfaceType.ts'; -import type { S_AngleThresholds } from '#root/Movement/Surface/S_AngleThresholds.ts'; -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -class BFL_SurfaceClassifierClass extends BlueprintFunctionLibrary { - // ════════════════════════════════════════════════════════════════════════════════════════ - // CLASSIFICATION - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Classify surface type based on normal vector and angle thresholds - * - * @param SurfaceNormal - Normalized surface normal vector (from hit result) - * @param AngleThresholdsRads - Angle thresholds in radians (pre-converted for performance) - * @returns Surface type classification - * - * @example - * // Flat ground (normal pointing up) - * const flat = SurfaceClassifier.Classify(new Vector(0, 0, 1), thresholds); - * // Returns: E_SurfaceType.Walkable - * - * @example - * // Steep slope (50° angle) - * const steep = SurfaceClassifier.Classify(BFL_Vectors.GetNormalFromAngle(50), thresholds); - * // Returns: E_SurfaceType.SteepSlope - * - * @pure true - * @category Classification - */ - public Classify( - SurfaceNormal: Vector, - AngleThresholdsRads: S_AngleThresholds - ): E_SurfaceType { - // Calculate angle between surface normal and up vector - const surfaceAngle = BFL_Vectors.GetSurfaceAngle(SurfaceNormal); - - // Classify based on angle thresholds - if (surfaceAngle <= AngleThresholdsRads.Walkable) { - return E_SurfaceType.Walkable; - } else if (surfaceAngle <= AngleThresholdsRads.SteepSlope) { - return E_SurfaceType.SteepSlope; - } else if (surfaceAngle <= AngleThresholdsRads.Wall) { - return E_SurfaceType.Wall; - } else { - return E_SurfaceType.Ceiling; - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // TYPE CHECKS - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Check if surface allows normal walking movement - * - * @param surfaceType - Surface type to check - * @returns True if surface is walkable - * - * @pure true - * @category Type Checks - */ - public IsWalkable(surfaceType: E_SurfaceType): boolean { - return surfaceType === E_SurfaceType.Walkable; - } - - /** - * Check if surface causes sliding behavior - * - * @param surfaceType - Surface type to check - * @returns True if surface is steep slope - * - * @pure true - * @category Type Checks - */ - public IsSteep(surfaceType: E_SurfaceType): boolean { - return surfaceType === E_SurfaceType.SteepSlope; - } - - /** - * Check if surface blocks movement (collision wall) - * - * @param surfaceType - Surface type to check - * @returns True if surface is a wall - * - * @pure true - * @category Type Checks - */ - public IsWall(surfaceType: E_SurfaceType): boolean { - return surfaceType === E_SurfaceType.Wall; - } - - /** - * Check if surface is overhead (ceiling) - * - * @param surfaceType - Surface type to check - * @returns True if surface is ceiling - * - * @pure true - * @category Type Checks - */ - public IsCeiling(surfaceType: E_SurfaceType): boolean { - return surfaceType === E_SurfaceType.Ceiling; - } - - /** - * Check if no surface detected (airborne state) - * - * @param surfaceType - Surface type to check - * @returns True if no surface contact - * - * @pure true - * @category Type Checks - */ - public IsNone(surfaceType: E_SurfaceType): boolean { - return surfaceType === E_SurfaceType.None; - } -} - -export const BFL_SurfaceClassifier = new BFL_SurfaceClassifierClass(); diff --git a/Content/Movement/Surface/BFL_SurfaceClassifier.uasset b/Content/Movement/Surface/BFL_SurfaceClassifier.uasset deleted file mode 100644 index 798eaeb..0000000 --- a/Content/Movement/Surface/BFL_SurfaceClassifier.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0d6074d5b47b4b596412be82b4fb81e29a4a3be4b228dd84bf36d16a78830c7a -size 97723 diff --git a/Content/Movement/Surface/E_SurfaceType.ts b/Content/Movement/Surface/E_SurfaceType.ts deleted file mode 100644 index d8404ae..0000000 --- a/Content/Movement/Surface/E_SurfaceType.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Movement/Surface/E_SurfaceType.ts - -export enum E_SurfaceType { - None = 'None', - Walkable = 'Walkable', - SteepSlope = 'SteepSlope', - Wall = 'Wall', - Ceiling = 'Ceiling', -} diff --git a/Content/Movement/Surface/E_SurfaceType.uasset b/Content/Movement/Surface/E_SurfaceType.uasset deleted file mode 100644 index f6091ef..0000000 --- a/Content/Movement/Surface/E_SurfaceType.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eea3fd14f52d5b91f0b357e2cedeadeaa5e1902e6349e03f9aff36e84aff755d -size 3256 diff --git a/Content/Movement/Surface/S_AngleThresholds.ts b/Content/Movement/Surface/S_AngleThresholds.ts deleted file mode 100644 index cc0a81f..0000000 --- a/Content/Movement/Surface/S_AngleThresholds.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Movement/Surface/S_AngleThresholds.ts - -import type { Float } from '#root/UE/Float.ts'; - -export interface S_AngleThresholds { - Walkable: Float; - SteepSlope: Float; - Wall: Float; -} diff --git a/Content/Movement/Surface/S_AngleThresholds.uasset b/Content/Movement/Surface/S_AngleThresholds.uasset deleted file mode 100644 index eeaa11f..0000000 --- a/Content/Movement/Surface/S_AngleThresholds.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e5be434c800b582dae02fe4612b2372bd08f551d24ccf42541b252cf1f0a52ed -size 5902 diff --git a/Content/Movement/TDD.md b/Content/Movement/TDD.md deleted file mode 100644 index abd3b5a..0000000 --- a/Content/Movement/TDD.md +++ /dev/null @@ -1,1304 +0,0 @@ -# Movement System - Technical Documentation (Stage 9 Refactored) - -## Обзор -Детерминированная система движения для 3D-платформера, построенная на принципе **Functional Core, Imperative Shell**. Система обеспечивает математически предсказуемое поведение через композицию чистых функциональных модулей, с точной классификацией поверхностей, swept collision detection и ground snapping. - -**Версия:** Stage 9 (Post-Refactoring) -**Архитектурный паттерн:** Pipeline Processor + Pure Function Libraries -**Статус:** Production Ready ✅ - ---- - -## Архитектурные принципы - -### Core Design Philosophy -1. **Functional Core, Imperative Shell** - - Pure business logic в BFL_* модулях - - Imperative framework integration в AC_Movement - -2. **Immutable State Transformation** - - `S_MovementState → ProcessMovement() → S_MovementState` - - Никогда не мутируем входящее состояние - -3. **Pipeline Processing** - - Четкие последовательные фазы обработки - - Каждая фаза читает результаты предыдущей - -4. **Separation of Concerns** - - AC_Movement: координация и framework integration - - BFL_MovementProcessor: бизнес-логика движения - - BFL_*: специализированные подсистемы - -5. **Data-Oriented Design** - - Явные структуры данных (S_MovementState, S_MovementInput) - - Функции оперируют данными, не прячут их - ---- - -## Компоненты системы - -### 1. AC_Movement (Component - Imperative Shell) - -**Роль:** Thin orchestration layer между UE Actor system и функциональной логикой - -**Ответственности:** -- Lifecycle management (инициализация, cleanup) -- Component references (CapsuleComponent, DebugHUD) -- State storage (CurrentMovementState) -- Framework integration (SetActorLocation, SetActorRotation) -- Debug visualization coordination - -**Ключевые методы:** - -```typescript -// Инициализация системы -InitializeMovementSystem( - CapsuleComponentRef: CapsuleComponent | null, - DebugHUDComponentRef: AC_DebugHUD | null -): void - -// Обработка движения (главная точка входа) -ProcessMovementInput( - InputVector: Vector, - DeltaTime: Float -): void - -// Debug -UpdateDebugPage(): void -``` - -**Приватные поля:** -```typescript -CurrentMovementState: S_MovementState // Текущее состояние -Config: DA_MovementConfig // Конфигурация -AngleThresholdsRads: S_AngleThresholds // Кэшированные пороги в радианах -CapsuleComponent: CapsuleComponent | null // Ссылка на капсулу -DebugHUDComponent: AC_DebugHUD | null // Ссылка на debug HUD -IsInitialized: boolean // Флаг инициализации -``` - -**Размер:** ~230 LOC (↓ 62% от original) - ---- - -### 2. BFL_MovementProcessor (Core - Functional Heart) - -**Роль:** Unified movement processing pipeline - центральная точка всей логики движения - -**Ответственности:** -- Orchestration всех подсистем в правильном порядке -- State transformation (CurrentState + Input → NextState) -- Phase sequencing и data flow -- Integration между subsystems - -**Главный метод:** - -```typescript -ProcessMovement( - CurrentState: S_MovementState, - Input: S_MovementInput, - IsShowVisualDebug: boolean = false -): S_MovementState -``` - -**Processing Pipeline (6 фаз):** - -```typescript -// PHASE 1: INPUT & ROTATION -├─ Calculate input magnitude -└─ Update character rotation (BFL_RotationController) - -// PHASE 2: GROUND DETECTION -├─ Check ground with trace (BFL_GroundProbe) -├─ Determine IsGrounded -└─ Classify surface type (BFL_SurfaceClassifier) - -// PHASE 3: PHYSICS CALCULATION -├─ Calculate ground velocity (BFL_Kinematics) [if grounded] -│ OR Apply air friction (BFL_Kinematics) [if airborne] -├─ Apply gravity (BFL_Kinematics) -└─ Calculate horizontal speed - -// PHASE 4: MOVEMENT APPLICATION (Sweep) -├─ Convert velocity to delta -├─ Perform swept collision (BFL_CollisionResolver) -└─ Calculate slide vector if blocked - -// PHASE 5: GROUND SNAPPING -└─ Snap to ground if conditions met (BFL_GroundProbe) - -// PHASE 6: STATE DETERMINATION -└─ Determine movement state (BFL_MovementStateMachine) - -// RETURN: Complete new S_MovementState -``` - -**Вспомогательные методы:** -```typescript -CreateInitialState( - Location: Vector, - Rotation: Rotator -): S_MovementState -``` - -**Purity:** Impure (из-за collision traces), но deterministic -**Размер:** ~260 LOC - ---- - -### 3. BFL_Kinematics (Physics Library) - -**Роль:** Pure physics calculations для движения - -**Ключевые методы:** - -```typescript -// Ground movement с acceleration -CalculateGroundVelocity( - CurrentVelocity: Vector, - InputVector: Vector, - DeltaTime: Float, - Config: DA_MovementConfig -): Vector - -// Friction (deceleration) -CalculateFriction( - CurrentVelocity: Vector, - DeltaTime: Float, - Config: DA_MovementConfig -): Vector - -// Gravity application -CalculateGravity( - CurrentVelocity: Vector, - IsGrounded: boolean, - Config: DA_MovementConfig -): Vector - -// Horizontal speed query -GetHorizontalSpeed(Velocity: Vector): Float -``` - -**Характеристики:** -- ✅ Pure functions -- ✅ VInterpTo для smooth movement -- ✅ Frame-rate independent (uses DeltaTime) - ---- - -### 4. BFL_CollisionResolver (Collision Library) - -**Роль:** Swept collision detection и surface sliding - -**Ключевые методы:** - -```typescript -// Главный swept trace -PerformSweep( - StartLocation: Vector, - DesiredDelta: Vector, - CapsuleComponent: CapsuleComponent | null, - Config: DA_MovementConfig, - DeltaTime: Float, - IsShowVisualDebug: boolean -): S_SweepResult - -// Surface sliding projection -ProjectOntoSurface( - MovementDelta: Vector, - SurfaceNormal: Vector -): Vector - -// Slide vector calculation -CalculateSlideVector( - SweepResult: S_SweepResult, - OriginalDelta: Vector, - StartLocation: Vector -): Vector - -// Adaptive step size для swept trace -CalculateStepSize( - Velocity: Vector, - DeltaTime: Float, - Config: DA_MovementConfig -): Float -``` - -**Характеристики:** -- ⚠️ Impure (world traces) -- ✅ Deterministic stepping -- ✅ Tunneling protection -- ✅ Adaptive precision - ---- - -### 5. BFL_GroundProbe (Ground Detection Library) - -**Роль:** Ground detection, snapping, surface queries - -**Ключевые методы:** - -```typescript -// Ground detection trace -CheckGround( - CharacterLocation: Vector, - CapsuleComponent: CapsuleComponent | null, - AngleThresholdsRads: S_AngleThresholds, - Config: DA_MovementConfig, - IsShowVisualDebug: boolean -): HitResult - -// Ground snapping calculation -CalculateSnapLocation( - CurrentLocation: Vector, - GroundHit: HitResult, - CapsuleComponent: CapsuleComponent | null, - SnapThreshold: Float -): Vector - -// Snapping condition check -ShouldSnapToGround( - CurrentVelocityZ: Float, - GroundHit: HitResult, - IsGrounded: boolean -): boolean - -// Surface type query -GetSurfaceType( - GroundHit: HitResult, - AngleThresholdsRads: S_AngleThresholds -): E_SurfaceType -``` - -**Характеристики:** -- ⚠️ Impure (LineTraceByChannel) -- ✅ Separate snapping logic -- ✅ Clear condition checking - ---- - -### 6. BFL_RotationController (Rotation Library) - -**Роль:** Character rotation toward movement direction - -**Ключевые методы:** - -```typescript -// Calculate target yaw from direction -CalculateTargetYaw(MovementDirection: Vector): Float - -// Calculate full target rotation -CalculateTargetRotation(MovementDirection: Vector): Rotator - -// Smooth rotation interpolation -InterpolateRotation( - CurrentRotation: Rotator, - TargetRotation: Rotator, - RotationSpeed: Float, - DeltaTime: Float, - MinSpeedForRotation: Float, - CurrentSpeed: Float -): S_RotationResult - -// Convenience method -UpdateRotation( - CurrentRotation: Rotator, - MovementDirection: Vector, - Config: DA_MovementConfig, - DeltaTime: Float, - CurrentSpeed: Float -): S_RotationResult -``` - -**Характеристики:** -- ✅ Pure functions -- ✅ Wraparound handling (180°/-180°) -- ✅ Min speed threshold - ---- - -### 7. BFL_MovementStateMachine (State Machine) - -**Роль:** Determine movement state from context - -**Ключевые методы:** - -```typescript -// Main state determination -DetermineState(Context: S_MovementContext): E_MovementState - -// Internal helpers -private DetermineAirborneState(Context: S_MovementContext): E_MovementState -private DetermineGroundedState(Context: S_MovementContext): E_MovementState -``` - -**State Priority Logic:** -``` -1. IsGrounded? - ├─ Yes: Check surface type - │ ├─ SteepSlope → Sliding - │ ├─ Wall/Ceiling → Blocked - │ └─ Walkable → Check input - │ ├─ Has input & speed > 1.0 → Walking - │ └─ Else → Idle - └─ No → Airborne -``` - -**Характеристики:** -- ✅ Pure FSM logic -- ✅ Priority-based transitions -- ✅ Clear state rules - ---- - -### 8. BFL_SurfaceClassifier (Surface Classification) - -**Роль:** Classify surface based on normal angle - -**Ключевые методы:** - -```typescript -// Main classification -Classify( - SurfaceNormal: Vector, - AngleThresholdsRads: S_AngleThresholds -): E_SurfaceType - -// Type checking helpers -IsWalkable(surfaceType: E_SurfaceType): boolean -IsSteep(surfaceType: E_SurfaceType): boolean -IsWall(surfaceType: E_SurfaceType): boolean -IsCeiling(surfaceType: E_SurfaceType): boolean -IsNone(surfaceType: E_SurfaceType): boolean -``` - -**Classification Rules:** -``` -Surface Angle → Type -───────────────────── -≤ Walkable → Walkable (0°-50°) -≤ SteepSlope → SteepSlope (50°-85°) -≤ Wall → Wall (85°-95°) -> Wall → Ceiling (95°-180°) -``` - -**Характеристики:** -- ✅ Pure functions -- ✅ Angle-based classification -- ✅ Type-safe queries - ---- - -## Структуры данных - -### S_MovementState (Complete State Snapshot) - -**Роль:** Immutable snapshot всего состояния движения - -```typescript -interface S_MovementState { - // ═══════════════════════════════════════════════════════ - // TRANSFORM - // ═══════════════════════════════════════════════════════ - Location: Vector // World location - Rotation: Rotator // Yaw rotation - - // ═══════════════════════════════════════════════════════ - // VELOCITY & PHYSICS - // ═══════════════════════════════════════════════════════ - Velocity: Vector // Current velocity (cm/s) - Speed: Float // Horizontal speed (cm/s) - - // ═══════════════════════════════════════════════════════ - // GROUND STATE - // ═══════════════════════════════════════════════════════ - IsGrounded: boolean // On walkable ground? - GroundHit: HitResult // Ground trace result - SurfaceType: E_SurfaceType // Current surface classification - - // ═══════════════════════════════════════════════════════ - // COLLISION STATE - // ═══════════════════════════════════════════════════════ - IsBlocked: boolean // Blocked by collision? - CollisionCount: number // Collision checks this frame - - // ═══════════════════════════════════════════════════════ - // ROTATION STATE - // ═══════════════════════════════════════════════════════ - IsRotating: boolean // Currently rotating? - RotationDelta: Float // Remaining angular distance (degrees) - - // ═══════════════════════════════════════════════════════ - // MOVEMENT STATE - // ═══════════════════════════════════════════════════════ - MovementState: E_MovementState // Current FSM state - InputMagnitude: Float // Input magnitude (0-1) -} -``` - -**Usage Pattern:** -```typescript -// Immutable transformation -const newState = BFL_MovementProcessor.ProcessMovement( - currentState, // Never modified - input, - debugFlag -); - -// Apply to actor -this.GetOwner().SetActorLocation(newState.Location); -this.GetOwner().SetActorRotation(newState.Rotation); - -// Store for next frame -this.CurrentMovementState = newState; -``` - ---- - -### S_MovementInput (Input Encapsulation) - -**Роль:** All data needed для movement processing - -```typescript -interface S_MovementInput { - InputVector: Vector // Player input (normalized XY) - DeltaTime: Float // Frame delta time (seconds) - CapsuleComponent: CapsuleComponent | null // Collision capsule - Config: DA_MovementConfig // Movement config - AngleThresholdsRads: S_AngleThresholds // Surface thresholds (radians) -} -``` - -**Usage:** -```typescript -const input: S_MovementInput = { - InputVector: playerInput, - DeltaTime: deltaTime, - CapsuleComponent: this.CapsuleComponent, - Config: this.Config, - AngleThresholdsRads: this.AngleThresholdsRads -}; - -const newState = BFL_MovementProcessor.ProcessMovement( - this.CurrentMovementState, - input, - this.DebugHUDComponent?.ShowVisualDebug ?? false -); -``` - ---- - -### S_MovementContext (State Machine Input) - -**Роль:** Context для state determination - -```typescript -interface S_MovementContext { - IsGrounded: boolean // On walkable ground? - SurfaceType: E_SurfaceType // Surface classification - InputMagnitude: Float // Input strength (0-1) - CurrentSpeed: Float // Horizontal speed (cm/s) - VerticalVelocity: Float // Z velocity (cm/s) - IsBlocked: boolean // Blocked by collision? -} -``` - ---- - -### DA_MovementConfig (Configuration Asset) - -**Роль:** Centralized movement constants - -```typescript -class DA_MovementConfig extends PrimaryDataAsset { - // ═══════════════════════════════════════════════════════ - // MOVEMENT PHYSICS - // ═══════════════════════════════════════════════════════ - readonly MaxSpeed: Float = 800.0 // Max horizontal speed (cm/s) - readonly Acceleration: Float = 10.0 // VInterpTo acceleration rate - readonly Friction: Float = 8.0 // VInterpTo friction rate - readonly Gravity: Float = 980.0 // Gravity (cm/s²) - - // ═══════════════════════════════════════════════════════ - // SURFACE DETECTION - // ═══════════════════════════════════════════════════════ - readonly AngleThresholdsDegrees: S_AngleThresholds = { - Walkable: 50.0, // ≤50° = walkable - SteepSlope: 85.0, // ≤85° = steep slope - Wall: 95.0 // ≤95° = wall - } - - // ═══════════════════════════════════════════════════════ - // COLLISION SETTINGS - // ═══════════════════════════════════════════════════════ - readonly GroundTraceDistance: Float = 50.0 // Ground detection distance - readonly MinStepSize: Float = 1.0 // Min sweep step size - readonly MaxStepSize: Float = 50.0 // Max sweep step size - readonly MaxCollisionChecks: Float = 25 // Max checks per frame - - // ═══════════════════════════════════════════════════════ - // CHARACTER ROTATION - // ═══════════════════════════════════════════════════════ - RotationSpeed: Float = 360.0 // Rotation speed (deg/s) - MinSpeedForRotation: Float = 50.0 // Min speed to rotate - ShouldRotateToMovement: boolean = true // Enable rotation -} -``` - ---- - -### Enums - -```typescript -// Movement FSM states -enum E_MovementState { - Idle = 'Idle', // Stationary on ground - Walking = 'Walking', // Moving on ground - Airborne = 'Airborne', // In the air - Sliding = 'Sliding', // Sliding on steep slope - Blocked = 'Blocked' // Blocked by collision -} - -// Surface classification -enum E_SurfaceType { - None = 'None', // No ground contact - Walkable = 'Walkable', // Normal walking ≤50° - SteepSlope = 'SteepSlope', // Sliding 50°-85° - Wall = 'Wall', // Collision 85°-95° - Ceiling = 'Ceiling' // Overhead >95° -} -``` - ---- - -## Data Flow Diagram - -``` -┌──────────────────────────────────────────────────────────────┐ -│ AC_Movement │ -│ (Imperative Shell) │ -├──────────────────────────────────────────────────────────────┤ -│ │ -│ ProcessMovementInput(InputVector, DeltaTime) │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────┐ │ -│ │ Prepare S_MovementInput│ │ -│ └────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────┐ │ -│ │ BFL_MovementProcessor │ │ -│ │ .ProcessMovement() │ │ -│ │ │ │ -│ │ ┌─────────────────────────────┐ │ │ -│ │ │ PHASE 1: Input & Rotation │ │ │ -│ │ │ • Calculate input magnitude │ │ │ -│ │ │ • BFL_RotationController │ │ │ -│ │ └─────────────────────────────┘ │ │ -│ │ │ │ │ -│ │ ┌─────────▼───────────────────┐ │ │ -│ │ │ PHASE 2: Ground Detection │ │ │ -│ │ │ • BFL_GroundProbe │ │ │ -│ │ │ • BFL_SurfaceClassifier │ │ │ -│ │ └─────────────────────────────┘ │ │ -│ │ │ │ │ -│ │ ┌─────────▼───────────────────┐ │ │ -│ │ │ PHASE 3: Physics │ │ │ -│ │ │ • BFL_Kinematics │ │ │ -│ │ │ - Ground velocity / Friction│ │ │ -│ │ │ - Gravity │ │ │ -│ │ └─────────────────────────────┘ │ │ -│ │ │ │ │ -│ │ ┌─────────▼───────────────────┐ │ │ -│ │ │ PHASE 4: Movement (Sweep) │ │ │ -│ │ │ • BFL_CollisionResolver │ │ │ -│ │ │ - Perform sweep │ │ │ -│ │ │ - Calculate slide │ │ │ -│ │ └─────────────────────────────┘ │ │ -│ │ │ │ │ -│ │ ┌─────────▼───────────────────┐ │ │ -│ │ │ PHASE 5: Ground Snapping │ │ │ -│ │ │ • BFL_GroundProbe │ │ │ -│ │ └─────────────────────────────┘ │ │ -│ │ │ │ │ -│ │ ┌─────────▼───────────────────┐ │ │ -│ │ │ PHASE 6: State Determination│ │ │ -│ │ │ • BFL_MovementStateMachine │ │ │ -│ │ └─────────────────────────────┘ │ │ -│ │ │ │ │ -│ │ ┌─────────▼───────────────────┐ │ │ -│ │ │ Return S_MovementState │ │ │ -│ │ └─────────────────────────────┘ │ │ -│ └─────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────┐ │ -│ │ Apply to Actor │ │ -│ │ • SetActorLocation() │ │ -│ │ • SetActorRotation() │ │ -│ └────────────────────────┘ │ -│ │ -└──────────────────────────────────────────────────────────────┘ -``` - ---- - -## Processing Pipeline Details - -### Phase 1: Input & Rotation -```typescript -// Calculate input strength -const inputMagnitude = VectorLength(InputVector); - -// Update rotation -const rotationResult = BFL_RotationController.UpdateRotation( - CurrentState.Rotation, - InputVector, - Config, - DeltaTime, - CurrentState.Speed -); -``` - -**Output:** `rotationResult.Rotation`, `rotationResult.IsRotating` - ---- - -### Phase 2: Ground Detection -```typescript -// Perform ground trace -const groundHit = BFL_GroundProbe.CheckGround( - CurrentState.Location, - CapsuleComponent, - AngleThresholdsRads, - Config -); - -// Determine ground state -const isGrounded = groundHit.BlockingHit; - -// Classify surface -const surfaceType = BFL_GroundProbe.GetSurfaceType( - groundHit, - AngleThresholdsRads -); -``` - -**Output:** `groundHit`, `isGrounded`, `surfaceType` - ---- - -### Phase 3: Physics Calculation -```typescript -let newVelocity = CurrentState.Velocity; - -// Ground movement OR air friction -if (IsWalkable(surfaceType) && isGrounded) { - newVelocity = BFL_Kinematics.CalculateGroundVelocity( - newVelocity, - InputVector, - DeltaTime, - Config - ); -} else { - newVelocity = BFL_Kinematics.CalculateFriction( - newVelocity, - DeltaTime, - Config - ); -} - -// Apply gravity -newVelocity = BFL_Kinematics.CalculateGravity( - newVelocity, - isGrounded, - Config -); - -// Calculate speed -const newSpeed = BFL_Kinematics.GetHorizontalSpeed(newVelocity); -``` - -**Output:** `newVelocity`, `newSpeed` - ---- - -### Phase 4: Movement Application (Sweep) -```typescript -// Convert velocity to displacement -const desiredDelta = newVelocity * DeltaTime; - -// Perform swept collision -const sweepResult = BFL_CollisionResolver.PerformSweep( - CurrentState.Location, - desiredDelta, - CapsuleComponent, - Config, - DeltaTime, - IsShowVisualDebug -); - -let finalLocation = sweepResult.Location; - -// Handle collision sliding -if (sweepResult.Blocked) { - const slideVector = BFL_CollisionResolver.CalculateSlideVector( - sweepResult, - desiredDelta, - CurrentState.Location - ); - - // Apply slide if valid - if (VectorLength(slideVector) > 0.5 && - Dot(Normal(slideVector), sweepResult.Hit.ImpactNormal) >= -0.1) { - finalLocation = sweepResult.Location + slideVector; - } -} -``` - -**Output:** `finalLocation`, `sweepResult` - ---- - -### Phase 5: Ground Snapping -```typescript -if (BFL_GroundProbe.ShouldSnapToGround( - newVelocity.Z, - groundHit, - isGrounded -)) { - finalLocation = BFL_GroundProbe.CalculateSnapLocation( - finalLocation, - groundHit, - CapsuleComponent, - Config.GroundTraceDistance - ); -} -``` - -**Output:** `finalLocation` (potentially snapped) - ---- - -### Phase 6: State Determination -```typescript -const movementState = BFL_MovementStateMachine.DetermineState({ - IsGrounded: isGrounded, - SurfaceType: surfaceType, - InputMagnitude: inputMagnitude, - CurrentSpeed: newSpeed, - VerticalVelocity: newVelocity.Z, - IsBlocked: sweepResult.Blocked -}); -``` - -**Output:** `movementState` (E_MovementState) - ---- - -### Final State Construction -```typescript -return { - Location: finalLocation, - Rotation: rotationResult.Rotation, - Velocity: newVelocity, - Speed: newSpeed, - IsGrounded: isGrounded, - GroundHit: groundHit, - SurfaceType: surfaceType, - IsBlocked: sweepResult.Blocked, - CollisionCount: sweepResult.CollisionCount, - IsRotating: rotationResult.IsRotating, - RotationDelta: rotationResult.RemainingDelta, - MovementState: movementState, - InputMagnitude: inputMagnitude -}; -``` - ---- - -## API Reference - -### AC_Movement - -#### InitializeMovementSystem() -```typescript -InitializeMovementSystem( - CapsuleComponentRef: CapsuleComponent | null = null, - DebugHUDComponentRef: AC_DebugHUD | null = null -): void -``` - -**Описание:** Инициализирует систему движения -**Эффекты:** -- Sets `IsInitialized = true` -- Converts angle thresholds degrees → radians -- Creates initial movement state -- Registers debug page if HUD provided - -**Пример:** -```typescript -this.MovementComponent.InitializeMovementSystem( - this.CharacterCapsule, - this.DebugHUDComponent -); -``` - ---- - -#### ProcessMovementInput() -```typescript -ProcessMovementInput( - InputVector: Vector, - DeltaTime: Float -): void -``` - -**Описание:** Main movement processing entry point -**Параметры:** -- `InputVector` - Camera-relative movement input (normalized) -- `DeltaTime` - Frame delta time (seconds) - -**Flow:** -1. Constructs S_MovementInput -2. Calls BFL_MovementProcessor.ProcessMovement() -3. Applies resulting Location and Rotation to actor - -**Пример:** -```typescript -// In EventTick -this.MovementComponent.ProcessMovementInput( - this.CalculateMovementInput(), - DeltaSeconds -); -``` - ---- - -### BFL_MovementProcessor - -#### ProcessMovement() -```typescript -ProcessMovement( - CurrentState: S_MovementState, - Input: S_MovementInput, - IsShowVisualDebug: boolean = false -): S_MovementState -``` - -**Описание:** Unified movement processing - executes all 6 phases -**Параметры:** -- `CurrentState` - Current movement state (immutable) -- `Input` - Movement input data -- `IsShowVisualDebug` - Show debug traces in world - -**Возвращает:** New complete movement state - -**Purity:** Impure (collision traces), but deterministic - -**Пример:** -```typescript -const newState = BFL_MovementProcessor.ProcessMovement( - this.CurrentMovementState, - { - InputVector: inputVector, - DeltaTime: deltaTime, - CapsuleComponent: this.CapsuleComponent, - Config: this.Config, - AngleThresholdsRads: this.AngleThresholdsRads - }, - this.ShowDebug -); - -// Apply results -this.GetOwner().SetActorLocation(newState.Location); -this.GetOwner().SetActorRotation(newState.Rotation); -this.CurrentMovementState = newState; -``` - ---- - -#### CreateInitialState() -```typescript -CreateInitialState( - Location: Vector, - Rotation: Rotator -): S_MovementState -``` - -**Описание:** Creates initial movement state with defaults -**Purity:** Pure - -**Пример:** -```typescript -this.CurrentMovementState = BFL_MovementProcessor.CreateInitialState( - this.GetOwner().GetActorLocation(), - this.GetOwner().GetActorRotation() -); -``` - ---- - -## Best Practices - -### Initialization -```typescript -// ✅ Good - proper initialization order -class BP_MainCharacter extends Character { - private MovementComponent: AC_Movement; - - ReceiveBeginPlay(): void { - this.MovementComponent.InitializeMovementSystem( - this.GetCapsuleComponent(), - this.DebugHUDComponent - ); - } -} - -// ❌ Bad - using before initialization -ReceiveBeginPlay(): void { - this.MovementComponent.ProcessMovementInput(input, dt); // Will early-return! -} -``` - ---- - -### State Access -```typescript -// ✅ Good - direct state access -const currentSpeed = this.MovementComponent.CurrentMovementState.Speed; -const isGrounded = this.MovementComponent.CurrentMovementState.IsGrounded; - -// 🔄 Alternative - expose via getter -class AC_Movement { - public GetMovementState(): S_MovementState { - return this.CurrentMovementState; - } -} - -const state = this.MovementComponent.GetMovementState(); -const speed = state.Speed; -``` - ---- - -### Testing -```typescript -// ✅ Good - test processor directly -describe('BFL_MovementProcessor', () => { - it('should accelerate on ground', () => { - const initialState = BFL_MovementProcessor.CreateInitialState( - new Vector(0, 0, 100), - new Rotator(0, 0, 0) - ); - - const input: S_MovementInput = { - InputVector: new Vector(1, 0, 0), - DeltaTime: 0.016, - CapsuleComponent: mockCapsule, - Config: testConfig, - AngleThresholdsRads: testThresholds - }; - - const newState = BFL_MovementProcessor.ProcessMovement( - initialState, - input, - false - ); - - expect(newState.Velocity.X).toBeGreaterThan(0); - expect(newState.MovementState).toBe(E_MovementState.Walking); - }); -}); -``` - ---- - -### Performance -```typescript -// ✅ Good - check initialization once -if (this.MovementComponent.IsInitialized) { - // Movement processing -} - -// ✅ Good - cache config access -const maxSpeed = this.Config.MaxSpeed; -for (let i = 0; i < 100; i++) { - // Use maxSpeed -} - -// ❌ Bad - repeated property access -for (let i = 0; i < 100; i++) { - if (speed > this.Config.MaxSpeed) { ... } -} -``` - ---- - -## Configuration Guidelines - -### Movement Physics -| Parameter | Default | Description | Tuning Guide | -|-----------|---------|-------------|--------------| -| MaxSpeed | 800.0 | Max horizontal speed (cm/s) | Higher = faster character | -| Acceleration | 10.0 | VInterpTo acceleration rate | Higher = more responsive | -| Friction | 8.0 | VInterpTo friction rate | Higher = faster stopping | -| Gravity | 980.0 | Gravity force (cm/s²) | Standard Earth gravity | - -### Surface Detection -| Parameter | Default | Description | -|-----------|---------|-------------| -| Walkable | 50° | Max walkable angle | -| SteepSlope | 85° | Max steep slope angle | -| Wall | 95° | Max wall angle | - -### Collision -| Parameter | Default | Description | Performance Impact | -|-----------|---------|-------------|-------------------| -| MinStepSize | 1.0 | Min sweep step (cm) | Smaller = more precise, slower | -| MaxStepSize | 50.0 | Max sweep step (cm) | Larger = faster, less precise | -| MaxCollisionChecks | 25 | Max checks per frame | Higher = safer, more expensive | -| GroundTraceDistance | 50.0 | Ground detection distance (cm) | Balance precision vs cost | - -### Rotation -| Parameter | Default | Description | -|-----------|---------|-------------| -| RotationSpeed | 360.0 | Rotation speed (deg/s) | -| MinSpeedForRotation | 50.0 | Min speed to rotate (cm/s) | -| ShouldRotateToMovement | true | Enable rotation | - ---- - -## Performance Characteristics - -### Frame Budget -- **Target:** <1ms per frame (60 FPS) -- **Typical:** 0.3-0.7ms -- **Max:** ~1.5ms (complex collision scenarios) - -### Bottlenecks -1. **Swept Collision** (most expensive) - - Multiple CapsuleTraceByChannel calls - - Adaptive stepping helps - - Monitor `CollisionCount` in debug HUD - -2. **Ground Detection** (moderate) - - Single LineTraceByChannel per frame - - Always runs (even airborne) - -3. **Physics Calculations** (cheap) - - Pure math operations - - VInterpTo is optimized - -### Optimization Tips -1. Increase `MinStepSize` if collision checks too high -2. Decrease `GroundTraceDistance` to minimum needed -3. Disable `ShouldRotateToMovement` for static NPCs -4. Use `IsShowVisualDebug = false` in production - ---- - -## Testing Strategy - -### Unit Testing (BFL_* modules) -```typescript -describe('BFL_Kinematics', () => { - it('should apply friction correctly', () => { - const velocity = new Vector(800, 0, 0); - const result = BFL_Kinematics.CalculateFriction( - velocity, - 0.016, - testConfig - ); - - expect(result.X).toBeLessThan(velocity.X); - expect(result.Z).toBe(0); - }); -}); -``` - -### Integration Testing (BFL_MovementProcessor) -```typescript -describe('Movement Pipeline', () => { - it('should process complete frame', () => { - const state = processFullFrame(initialState, input); - - expect(state.Location).not.toEqual(initialState.Location); - expect(state.Velocity).toBeDefined(); - expect(state.MovementState).toBeDefined(); - }); -}); -``` - -### Manual Testing (see ManualTestingChecklist.md) -- Ground detection -- Surface classification -- Collision response -- Rotation behavior -- Debug visualization - ---- - -## Known Limitations - -### Current Constraints -1. **Binary Ground State:** No partial contact detection -2. **Single Collision Shape:** Capsule only -3. **Frame-Dependent Stepping:** Sweep precision varies with framerate -4. **No Material Physics:** Surface material not considered -5. **Simple Sliding:** Basic projection, no advanced friction - -### By Design -1. **Capsule Component Required:** System assumes capsule collision -2. **Deterministic Traces:** Relies on UE physics determinism -3. **Horizontal Focus:** Optimized for ground-based movement -4. **No Network Code:** Not yet optimized for multiplayer - ---- - -## Future Extensions (Stage 10+) - -### Planned Features -- **Jump System:** Vertical velocity application, coyote time, jump buffering -- **Steep Slope Sliding:** Physics-based sliding on non-walkable surfaces -- **Moving Platforms:** Platform attachment and relative movement -- **Wall Running:** Advanced surface interaction -- **Ledge Detection:** Edge detection for platforming -- **Material-Based Physics:** Surface material awareness - ---- - -## File Structure - -``` -Content/ -├── Movement/ -│ ├── Components/ -│ │ └── AC_Movement.ts # Imperative shell (~230 LOC) -│ ├── Core/ -│ │ ├── BFL_MovementProcessor.ts # Functional core (~260 LOC) -│ │ ├── DA_MovementConfig.ts # Configuration asset -│ │ ├── DA_MovementConfigDefault.ts # Default config -│ │ ├── E_MovementState.ts # Movement states enum -│ │ ├── S_MovementInput.ts # Input structure -│ │ └── S_MovementState.ts # State structure -│ ├── Collision/ -│ │ ├── BFL_CollisionResolver.ts # Swept collision -│ │ ├── BFL_GroundProbe.ts # Ground detection -│ │ └── S_SweepResult.ts # Sweep result structure -│ ├── Physics/ -│ │ └── BFL_Kinematics.ts # Movement physics -│ ├── Rotation/ -│ │ ├── BFL_RotationController.ts # Rotation logic -│ │ └── S_RotationResult.ts # Rotation result structure -│ ├── State/ -│ │ ├── BFL_MovementStateMachine.ts # FSM logic -│ │ └── S_MovementContext.ts # State context structure -│ ├── Surface/ -│ │ ├── BFL_SurfaceClassifier.ts # Surface classification -│ │ ├── E_SurfaceType.ts # Surface types enum -│ │ └── S_AngleThresholds.ts # Angle thresholds structure -│ └── Documentation/ -│ ├── TDD.md # This document -│ └── ManualTestingChecklist.md # Testing procedures -└── Math/ - └── Libraries/ - └── BFL_Vectors.ts # Vector math utilities -``` - ---- - -## Troubleshooting - -### Character Falling Through Ground -**Symptoms:** Character drops through walkable surface -**Checks:** -1. `GroundTraceDistance > 0` -2. Ground has `Visibility` collision channel -3. `CapsuleComponent` initialized correctly -4. `IsInitialized == true` - -**Debug:** -```typescript -// Enable visual debug -this.DebugHUDComponent.ShowVisualDebug = true; -// Check ground hit in debug HUD -// Look for green line trace downward -``` - ---- - -### Excessive Collision Checks -**Symptoms:** `CollisionCount` consistently hitting `MaxCollisionChecks` -**Fixes:** -1. Increase `MaxStepSize` (careful: less precision) -2. Decrease `MaxSpeed` -3. Increase `MaxCollisionChecks` (careful: performance cost) -4. Check for collision geometry issues - -**Debug:** -```typescript -// Monitor in debug HUD -CollisionCount / MaxCollisionChecks -// Should be <50% most frames -``` - ---- - -### Jittery Z Position -**Symptoms:** Character bouncing up/down slightly -**Fixes:** -1. Verify ground detection working (`IsGrounded` in debug HUD) -2. Check ground snapping active -3. Slightly increase `GroundTraceDistance` -4. Verify ground has consistent collision - ---- - -### Character Not Rotating -**Symptoms:** Character faces wrong direction -**Checks:** -1. `Config.ShouldRotateToMovement == true` -2. `CurrentSpeed > Config.MinSpeedForRotation` -3. `SetActorRotation()` called each frame -4. Input vector not zero - -**Debug:** -```typescript -// Check in debug HUD -Is Rotating: true/false -Rotation Delta: [degrees remaining] -Current Yaw: [current angle] -``` - ---- - -## Conclusion - -Movement System представляет собой production-ready систему с следующими характеристиками: - -### Архитектурные достижения ✅ -- **Functional Core, Imperative Shell** pattern реализован полностью -- **Clear separation of concerns** между subsystems -- **Pipeline processing** с явными фазами -- **Immutable state transformations** везде где возможно -- **Testable design** благодаря pure function libraries - -### Production Readiness ✅ -- **Deterministic physics** с VInterpTo -- **Tunneling protection** через swept collision -- **Frame-rate independence** через DeltaTime -- **Performance optimized** (<1ms typical frame time) -- **Debug visualization** comprehensive -- **Extensible architecture** для future features - -### Code Quality ✅ -- **LOC reduced** 62% в AC_Movement (600 → 230) -- **Modularity** high - 8 focused modules -- **Documentation** comprehensive -- **Type safety** strong через TypeScript structs - -**Production Status:** ✅ **Ready for Stage 10** - -Архитектура готова для расширения Jump System и последующих features без major refactoring. diff --git a/Content/Toasts/Components/AC_ToastSystem.ts b/Content/Toasts/Components/AC_ToastSystem.ts index 263eb02..e277094 100644 --- a/Content/Toasts/Components/AC_ToastSystem.ts +++ b/Content/Toasts/Components/AC_ToastSystem.ts @@ -1,16 +1,16 @@ -// Toasts/Components/AC_ToastSystem.ts +// Content/Toasts/Components/AC_ToastSystem.ts -import type { S_ToastMessage } from '#root/Toasts/Structs/S_ToastMessage.ts'; -import type { WBP_Toast } from '#root/Toasts/UI/WBP_Toast.ts'; -import { WBP_ToastContainer } from '#root/Toasts/UI/WBP_ToastContainer.ts'; -import { ActorComponent } from '#root/UE/ActorComponent.ts'; -import { CreateWidget } from '#root/UE/CteateWidget.ts'; -import type { Float } from '#root/UE/Float.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import type { Text } from '#root/UE/Text.ts'; -import { UEArray } from '#root/UE/UEArray.ts'; -import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; +import type { S_ToastMessage } from '/Content/Toasts/Structs/S_ToastMessage.ts'; +import type { WBP_Toast } from '/Content/Toasts/UI/WBP_Toast.ts'; +import { WBP_ToastContainer } from '/Content/Toasts/UI/WBP_ToastContainer.ts'; +import { ActorComponent } from '/Content/UE/ActorComponent.ts'; +import { CreateWidget } from '/Content/UE/CteateWidget.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import type { Integer } from '/Content/UE/Integer.ts'; +import { SystemLibrary } from '/Content/UE/SystemLibrary.ts'; +import type { Text } from '/Content/UE/Text.ts'; +import { UEArray } from '/Content/UE/UEArray.ts'; +import { E_MessageType } from '/Content/UI/Enums/E_MessageType.ts'; /** * Toast Notification System Component diff --git a/Content/Toasts/ManualTestingChecklist.md b/Content/Toasts/ManualTestingChecklist.md deleted file mode 100644 index da36d53..0000000 --- a/Content/Toasts/ManualTestingChecklist.md +++ /dev/null @@ -1,89 +0,0 @@ -[//]: # (Toasts/ManualTestingChecklist.md) - -# Toast System - Manual Testing Checklist - -## Тестовая среда -- **Персонаж:** BP_MainCharacter с ShowDebugInfo = true -- **Требования:** ToastSystemComponent инициализирован - ---- - -## 1. Отображение toast уведомлений - -### 1.1 Базовое отображение -- [ ] **Toast появляются** в правильном месте на экране -- [ ] **Вертикальная укладка** - новые toast появляются снизу/сверху стека -- [ ] **Читаемость** - текст четко виден на игровом фоне - -### 1.2 Цветовая схема по типам -- [ ] **Info toast** - голубой фон (B:226, G:144, R:74) -- [ ] **Success toast** - зеленый фон (B:92, G:184, R:92) -- [ ] **Warning toast** - оранжевый фон (B:78, G:173, R:240) -- [ ] **Error toast** - красный фон (B:79, G:83, R:217) -- [ ] **Debug toast** - серый фон (B:125, G:117, R:108) - ---- - -## 2. Жизненный цикл toast - -### 2.1 Автоматическое исчезновение -- [ ] **Default duration (3 секунды)** - toast исчезают через 3 секунды -- [ ] **Custom duration** - toast с заданной длительностью исчезают в нужное время -- [ ] **Плавное удаление** - toast исчезают без резких скачков - -### 2.2 Лимит количества -- [ ] **MaxVisibleToasts = 5** - одновременно показано не больше 5 toast -- [ ] **Oldest removal** - при превышении лимита удаляются самые старые -- [ ] **FIFO поведение** - первый добавленный, первый удаленный - ---- - -## 3. Интеграция с другими системами - -### 3.1 Debug HUD интеграция -- [ ] **"Debug HUD Initialized"** - Success toast при инициализации Debug HUD -- [ ] **"Visual Debug Enabled/Disabled"** - Info toast при переключении F2 -- [ ] **No conflicts** - toast не перекрывают debug HUD - -### 3.2 Console logging -- [ ] **AlsoLogToConsole = true** - сообщения дублируются в консоль -- [ ] **Format:** "[MessageType] Message text" в консоли -- [ ] **All types logged** - все типы сообщений попадают в консоль - ---- - -## 4. Edge cases - -### 4.1 Различные типы сообщений -- [ ] **Empty message** - toast с пустым сообщением отображается -- [ ] **Long message** - длинные сообщения корректно отображаются -- [ ] **Multiline message** - сообщения с \n переносами работают -- [ ] **Special characters** - Unicode символы отображаются правильно - -### 4.2 Rapid creation -- [ ] **Быстрое создание** множества toast работает стабильно -- [ ] **No memory leaks** при создании большого количества уведомлений -- [ ] **Performance stable** - система не влияет на FPS при активном использовании - ---- - -## 5. Функциональные триггеры в игре - -### 5.1 Debug HUD события -- [ ] **F1 toggle** не генерирует лишних toast -- [ ] **F2 toggle** показывает состояние Visual Debug -- [ ] **Debug HUD init** показывает success notification один раз при старте - -### 5.2 System events -- [ ] **Startup messages** появляются при инициализации систем -- [ ] **No spam** - повторные события не создают избыточных toast -- [ ] **Proper timing** - toast появляются в нужный момент событий - ---- - -## Критерии прохождения -- [ ] Все типы toast отображаются с правильными цветами -- [ ] Лимит в 5 уведомлений соблюдается -- [ ] Toast исчезают через заданное время -- [ ] Интеграция с Debug HUD работает корректно -- [ ] Console logging работает при включенной настройке diff --git a/Content/Toasts/Structs/S_ToastMessage.ts b/Content/Toasts/Structs/S_ToastMessage.ts index 4df5335..85f3302 100644 --- a/Content/Toasts/Structs/S_ToastMessage.ts +++ b/Content/Toasts/Structs/S_ToastMessage.ts @@ -1,9 +1,9 @@ -// Toasts/Structs/S_ToastMessage.ts +// Content/Toasts/Structs/S_ToastMessage.ts -import type { Float } from '#root/UE/Float.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import type { Text } from '#root/UE/Text.ts'; -import type { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import type { Integer } from '/Content/UE/Integer.ts'; +import type { Text } from '/Content/UE/Text.ts'; +import type { E_MessageType } from '/Content/UI/Enums/E_MessageType.ts'; export interface S_ToastMessage { ID: Integer; diff --git a/Content/Toasts/TDD.md b/Content/Toasts/TDD.md deleted file mode 100644 index 307ac72..0000000 --- a/Content/Toasts/TDD.md +++ /dev/null @@ -1,321 +0,0 @@ -[//]: # (Toasts/TDD.md) - -# Система Toast - Техническая Документация - -## Обзор -Система уведомлений Toast для отображения временных информационных сообщений в игровом интерфейсе. Обеспечивает автоматическое управление жизненным циклом уведомлений, типизацию по важности и интеграцию с debug системами. Поддерживает до 5 одновременных уведомлений с автоматическим удалением устаревших. - -## Архитектурные принципы -- **Автоматический lifecycle:** Самоуправляемое создание и удаление toast уведомлений -- **Типизированные сообщения:** Цветовая дифференциация по типу (Info, Success, Warning, Error, Debug) -- **Ограниченная емкость:** Контролируемое количество видимых уведомлений -- **Integration ready:** Тесная интеграция с Debug HUD и другими системами -- **Instance-editable config:** Настройки доступны для изменения в Blueprint editor - -## Компоненты системы - -### AC_ToastSystem (Core Component) -**Ответственности:** -- Управление жизненным циклом toast уведомлений -- Контроль максимального количества видимых toast -- Автоматическое удаление expired уведомлений -- Интеграция с UI контейнером для позиционирования - -**Ключевые функции:** -- `InitializeToastSystem()` - Инициализация контейнера и системы -- `ShowToast()` - Создание нового уведомления с возвратом ID -- `UpdateToastSystem()` - Main loop для удаления expired toast -- `GetTestData()` - Возврат данных для тестирования - -### WBP_ToastContainer (UI Container) -**Ответственности:** -- Вертикальное позиционирование toast уведомлений -- Автоматическое управление layout и spacing -- Добавление и удаление child toast widgets -- Viewport integration для корректного отображения - -### WBP_Toast (Individual Widget) -**Ответственности:** -- Отображение текста уведомления -- Динамическое изменение цвета фона по типу сообщения -- Обновление содержимого в runtime - -### BFL_Colors (Color Management Library) -**Ответственности:** -- Цветовая схема для разных типов сообщений -- Консистентная стилизация across всей системы - -## Типы уведомлений - -### Message Types (E_MessageType) -```typescript -enum E_MessageType { - Info = 'Info', // Общая информация - Success = 'Success', // Успешные операции - Warning = 'Warning', // Предупреждения - Error = 'Error', // Ошибки - Debug = 'Debug' // Debug информация -} -``` - -### Цветовая схема -- **Info:** Синий (#0066CC) -- **Success:** Зеленый (#00CC66) -- **Warning:** Оранжевый (#FF9900) -- **Error:** Красный (#CC0000) -- **Debug:** Фиолетовый (#9933CC) - -## API Reference - -### ShowToast() -```typescript -public ShowToast( - Message: Text = '', - Type: E_MessageType = E_MessageType.Info, - Duration: Float = 5 -): Integer -``` -**Описание:** Создает и отображает новое toast уведомление -**Возвращает:** Toast ID (положительное число) или -1 при неудаче - -**Parameters:** -- `Message` - Текст уведомления -- `Type` - Тип сообщения (Info/Success/Warning/Error/Debug) -- `Duration` - Время отображения в секундах (по умолчанию 5) - -**Примеры:** -```typescript -// Стандартное использование -this.ToastComponent.ShowToast("Save complete", E_MessageType.Success) - -// Кастомная длительность -this.ToastComponent.ShowToast("Critical error!", E_MessageType.Error, 10) -``` - -### GetTestData() -```typescript -public GetTestData(): { - ToastWidgets: UEArray; - MaxVisibleToasts: Integer; - IsEnabled: boolean; -} -``` -**Описание:** Возвращает данные системы для тестирования -**Возвращает:** Объект с активными widgets и конфигурацией - -**Использование в тестах:** -```typescript -const data = this.ToastComponent.GetTestData() -this.AssertEqual(data.ToastWidgets.length, 5, "Should not exceed max") -this.AssertEqual(data.MaxVisibleToasts, 5, "Default limit check") -this.AssertTrue(data.IsEnabled, "System should be enabled") -``` - -### InitializeToastSystem() -```typescript -public InitializeToastSystem(): void -``` -**Описание:** Инициализирует систему, создает UI контейнер -**Обязательность:** Должна быть вызвана ДО любых вызовов ShowToast() - -### UpdateToastSystem() -```typescript -public UpdateToastSystem(): void -``` -**Описание:** Main loop функция, обрабатывает removal expired toast -**Вызов:** Должна вызываться каждый frame в Tick - -## Алгоритмы работы - -### Создание toast -``` -ShowToast(Message, Type, Duration): -1. ShouldProcessToasts() - проверка IsInitialized && IsEnabled -2. Создание S_ToastMessage с уникальным ID -3. EnforceToastLimit() - удаление oldest если >= MaxVisibleToasts -4. ToastContainer.AddToast() - создание widget -5. Add в ActiveToasts и ToastWidgets -6. LogToConsole() если AlsoLogToConsole = true -7. Return ID или -1 -``` - -### Удаление expired toast -``` -RemoveExpiredToasts() в UpdateToastSystem(): -1. Loop через ActiveToasts -2. Для каждого toast проверка: (CurrentTime - CreatedTime > Duration) -3. Если expired: - - ToastContainer.RemoveToast(widget) - - RemoveIndex() из ActiveToasts и ToastWidgets -``` - -### Контроль лимитов -``` -EnforceToastLimit(): -1. while (ActiveToasts.length >= MaxVisibleToasts) -2. Удаление oldest toast (index 0) -3. RemoveIndex(0) из обоих массивов -``` - -## Производительность - -### Benchmarks -- **Инициализация:** <1ms -- **ShowToast:** <0.1ms на создание -- **UpdateToastSystem:** <0.05ms при 5 активных toast -- **Memory footprint:** ~50 байт на активный toast - -## Система тестирования - -### FT_ToastsSystemInitialization -**Проверяет базовую инициализацию:** -- Корректность default settings (IsEnabled = true, MaxVisibleToasts = 5) -- Успешность InitializeToastSystem() - -### FT_ToastsDurationHandling -**Тестирует ID assignment:** -- ShowToast() возвращает валидные положительные ID -- Каждый toast получает уникальный ID - -### FT_ToastsToastCreation -**Валидирует создание по всем типам:** -- Info, Success, Warning, Error, Debug -- Все типы создают валидные widgets - -### FT_ToastLimit -**Проверяет контроль лимитов:** -- Создание MaxVisibleToasts + 3 уведомлений -- Проверка что отображается только MaxVisibleToasts -- Корректное удаление oldest при overflow - -### FT_ToastsEdgeCases -**Тестирует граничные условия:** -- Empty message -- Long message (500 символов) -- Multiline message - -## Интеграция с системами - -### С Debug HUD System -```typescript -this.ToastComponent.ShowToast('Debug HUD Initialized', E_MessageType.Success) -``` - -### С Main Character -```typescript -// В EventBeginPlay -this.ToastSystemComponent.InitializeToastSystem() - -// В Tick -this.ToastSystemComponent.UpdateToastSystem() -``` - -## Миграция с предыдущей версии - -### Изменения в рефакторинге -1. ✅ Убрана структура `S_ToastSettings` -2. ✅ Переменные стали прямыми полями компонента с `@instanceEditable` -3. ✅ `ShowToast()` теперь имеет `Duration: Float = 5` (было 0) -4. ✅ `GetTestData()` возвращает расширенный объект - -### Breaking Changes - -#### 1. Доступ к настройкам -```typescript -// ❌ Старый код -if (this.ToastComponent.ToastSettings.IsEnabled) { } - -// ✅ Новый код - используем GetTestData() -const data = this.ToastComponent.GetTestData() -if (data.IsEnabled) { } -``` - -#### 2. ShowToast Duration -```typescript -// ❌ Старый код - 0 означал default -this.ShowToast("Message", E_MessageType.Info, 0) - -// ✅ Новый код - просто не передавать -this.ShowToast("Message", E_MessageType.Info) -``` - -## Best Practices - -### Использование в коде -```typescript -// ✅ Хорошо - инициализация перед использованием -this.ToastSystemComponent.InitializeToastSystem() -this.ToastSystemComponent.ShowToast("Success!", E_MessageType.Success) - -// ✅ Хорошо - кастомная длительность -this.ToastSystemComponent.ShowToast("Error!", E_MessageType.Error, 10.0) - -// ❌ Плохо - использование без инициализации -this.ToastSystemComponent.ShowToast("Message") // вернет -1 -``` - -### Рекомендации по типам -- **Info:** Общая информация -- **Success:** Подтверждение операций -- **Warning:** Предупреждения -- **Error:** Критические ошибки -- **Debug:** Техническая информация - -### Рекомендации по Duration -- **1-2s:** Простые подтверждения -- **5s (default):** Большинство уведомлений -- **8-10s:** Errors, warnings, важные события - -## Troubleshooting - -### Toast не отображаются -- ✅ Проверить что `InitializeToastSystem()` вызван -- ✅ Проверить `IsEnabled = true` через `GetTestData()` -- ✅ Проверить что `UpdateToastSystem()` вызывается - -### Toast исчезают слишком быстро -- ✅ Передавать кастомную Duration в ShowToast() -- ✅ Проверить что время в секундах - -### Слишком много toast -- ✅ Настроить MaxVisibleToasts в Blueprint -- ✅ Группировать похожие уведомления - -## Файловая структура - -``` -Content/ -├── Toasts/ -│ ├── Components/ -│ │ └── AC_ToastSystem.ts -│ ├── Structs/ -│ │ └── S_ToastMessage.ts -│ ├── UI/ -│ │ ├── WBP_Toast.ts -│ │ └── WBP_ToastContainer.ts -│ └── Tests/ -│ ├── FT_ToastLimit.ts -│ ├── FT_ToastsDurationHandling.ts -│ ├── FT_ToastsEdgeCases.ts -│ ├── FT_ToastsSystemInitialization.ts -│ └── FT_ToastsToastCreation.ts -├── UI/ -│ ├── Enums/ -│ │ └── E_MessageType.ts -│ └── Libraries/ -│ └── BFL_Colors.ts -└── Blueprints/ - └── BP_MainCharacter.ts -``` - -## Заключение - -Toast System после рефакторинга представляет собой более чистую и maintainable архитектуру. - -**Ключевые достижения:** -- ✅ Упрощена структура (убрана S_ToastSettings) -- ✅ Улучшен API с явным default Duration = 5s -- ✅ GetTestData() предоставляет доступ к конфигурации -- ✅ Instance-editable переменные для Blueprint -- ✅ Полная test coverage -- ✅ Production-ready performance diff --git a/Content/Toasts/Tests/FT_ToastLimit.ts b/Content/Toasts/Tests/FT_ToastLimit.ts deleted file mode 100644 index 3de561f..0000000 --- a/Content/Toasts/Tests/FT_ToastLimit.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Toasts/Tests/FT_ToastLimit.ts - -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; - -/** - * Functional Test: Toast System Capacity Management - * Validates that the toast system enforces MaxVisibleToasts limit correctly - * Tests that oldest toasts are removed when limit is exceeded - */ -export class FT_ToastLimit extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test execution - validates toast limit enforcement - * Creates more toasts than allowed and verifies limit is enforced - */ - EventStartTest(): void { - this.ToastComponent.InitializeToastSystem(); - - // Create MaxVisibleToasts + 3 toasts to test overflow handling - for ( - let i = 1; - i <= this.ToastComponent.ToastSettings.MaxVisibleToasts + 3; - i++ - ) { - this.ToastComponent.ShowToast( - `Limit test toast ${i}`, - E_MessageType.Info, - 10 - ); - } - - // Verify that only MaxVisibleToasts are actually visible - if ( - this.ToastComponent.GetTestData().length === - this.ToastComponent.ToastSettings.MaxVisibleToasts - ) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Expected ${this.ToastComponent.ToastSettings.MaxVisibleToasts} to display, got ${this.ToastComponent.GetTestData().length}` - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Toast notification system - component under test - * @category Components - */ - private ToastComponent = new AC_ToastSystem(); -} diff --git a/Content/Toasts/Tests/FT_ToastLimit.uasset b/Content/Toasts/Tests/FT_ToastLimit.uasset deleted file mode 100644 index 09defc5..0000000 --- a/Content/Toasts/Tests/FT_ToastLimit.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1fd34735851babf7dda88322b1a69d926b023dc9d663759298f0278d519a6f97 -size 81663 diff --git a/Content/Toasts/Tests/FT_ToastsDurationHandling.ts b/Content/Toasts/Tests/FT_ToastsDurationHandling.ts deleted file mode 100644 index a841afe..0000000 --- a/Content/Toasts/Tests/FT_ToastsDurationHandling.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Toasts/Tests/FT_ToastsDurationHandling.ts - -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import type { Integer } from '#root/UE/Integer.ts'; - -/** - * Functional Test: Toast Duration and Lifecycle Management - * Validates basic toast creation and ID assignment functionality - * Tests that toasts return valid IDs when created successfully - */ -export class FT_ToastsDurationHandling extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test execution - validates basic toast creation functionality - * Creates two toasts and verifies they return valid IDs - */ - EventStartTest(): void { - this.ToastComponent.InitializeToastSystem(); - this.toast1 = this.ToastComponent.ShowToast(); - this.toast2 = this.ToastComponent.ShowToast(); - - /** - * Check if both toasts were created successfully by verifying positive IDs - */ - const AreToastsCreatedSuccessfully = (): boolean => - this.toast1 > 0 && this.toast2 > 0; - - if (AreToastsCreatedSuccessfully()) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest(EFunctionalTestResult.Failed, `Failed to create toasts`); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Toast notification system - component under test - * @category Components - */ - private ToastComponent = new AC_ToastSystem(); - - /** - * ID of first test toast - * @category Test State - */ - private toast1: Integer = 0; - - /** - * ID of second test toast - * @category Test State - */ - private toast2: Integer = 0; -} diff --git a/Content/Toasts/Tests/FT_ToastsDurationHandling.uasset b/Content/Toasts/Tests/FT_ToastsDurationHandling.uasset deleted file mode 100644 index 6c6a79a..0000000 --- a/Content/Toasts/Tests/FT_ToastsDurationHandling.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa2e41a9e21c220fe5937c5d66a272b3d976f6bf5970e509b8c9bd2c32134f45 -size 70614 diff --git a/Content/Toasts/Tests/FT_ToastsEdgeCases.ts b/Content/Toasts/Tests/FT_ToastsEdgeCases.ts deleted file mode 100644 index e806f71..0000000 --- a/Content/Toasts/Tests/FT_ToastsEdgeCases.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Toasts/Tests/FT_ToastsEdgeCases.ts - -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { StringLibrary } from '#root/UE/StringLibrary.ts'; -import type { Text } from '#root/UE/Text.ts'; -import { TextLibrary } from '#root/UE/TextLibrary.ts'; - -/** - * Functional Test: Toast System Edge Cases - * Tests toast system robustness with unusual input conditions - * Validates handling of empty, very long, and multiline messages - */ -export class FT_ToastsEdgeCases extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test preparation - generates very long text for stress testing - * Creates 500-character string to test text handling limits - */ - EventPrepareTest(): void { - for (let i = 0; i < 500; i++) { - this.longText = TextLibrary.StringToText( - StringLibrary.Append(TextLibrary.TextToString(this.longText), 'A') - ); - } - } - - /** - * Test execution - validates edge case handling - * Tests empty text, very long text, and multiline text scenarios - */ - EventStartTest(): void { - this.ToastComponent.InitializeToastSystem(); - - // Test empty message handling - this.emptyToast = this.ToastComponent.ShowToast(); - - // Test very long message handling (500 characters) - this.longToast = this.ToastComponent.ShowToast(this.longText); - - // Test multiline message handling - this.specialToast = this.ToastComponent.ShowToast(`Test -Multiline -Message`); - - /** - * Check if all edge case toasts were created successfully - */ - const AreToastsCreatedSuccessfully = (): boolean => - this.emptyToast > 0 && this.longToast > 0 && this.specialToast > 0; - - if (AreToastsCreatedSuccessfully()) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - `Edge case failures: empty=${this.emptyToast}, long=${this.longToast}, special=${this.specialToast}` - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Toast notification system - component under test - * @category Components - */ - private ToastComponent = new AC_ToastSystem(); - - /** - * ID of toast created with empty message - * @category Test State - */ - private emptyToast: Integer = 0; - - /** - * ID of toast created with very long message (500 chars) - * @category Test State - */ - private longToast: Integer = 0; - - /** - * ID of toast created with multiline message - * @category Test State - */ - private specialToast: Integer = 0; - - /** - * Generated long text string for stress testing - * Built during test preparation phase - * @category Test Data - */ - private longText: Text = ''; -} diff --git a/Content/Toasts/Tests/FT_ToastsEdgeCases.uasset b/Content/Toasts/Tests/FT_ToastsEdgeCases.uasset deleted file mode 100644 index 8767414..0000000 --- a/Content/Toasts/Tests/FT_ToastsEdgeCases.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6d5a2f4b44eaa5c15861b38af701ff6881eefbef4f4343a1256263d3fd190242 -size 124661 diff --git a/Content/Toasts/Tests/FT_ToastsSystemInitialization.ts b/Content/Toasts/Tests/FT_ToastsSystemInitialization.ts deleted file mode 100644 index 7a477b3..0000000 --- a/Content/Toasts/Tests/FT_ToastsSystemInitialization.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Toasts/Tests/FT_ToastsSystemInitialization.ts - -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; - -/** - * Functional Test: Toast System Initialization - * Validates that toast system initializes with correct default settings - * Tests IsEnabled state and MaxVisibleToasts configuration - */ -export class FT_ToastsSystemInitialization extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test execution - validates initialization and default settings - * Uses nested validation to check system state after initialization - */ - EventStartTest(): void { - this.ToastComponent.InitializeToastSystem(); - - if (this.ToastComponent.ToastSettings.IsEnabled) { - if (this.ToastComponent.ToastSettings.MaxVisibleToasts > 0) { - this.FinishTest(EFunctionalTestResult.Succeeded); - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Invalid state: max visible toasts less then 1' - ); - } - } else { - this.FinishTest( - EFunctionalTestResult.Failed, - 'Invalid state: enabled=false' - ); - } - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Toast notification system - component under test - * @category Components - */ - private ToastComponent = new AC_ToastSystem(); -} diff --git a/Content/Toasts/Tests/FT_ToastsSystemInitialization.uasset b/Content/Toasts/Tests/FT_ToastsSystemInitialization.uasset deleted file mode 100644 index a75fd0d..0000000 --- a/Content/Toasts/Tests/FT_ToastsSystemInitialization.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6114ed8c34b1b8c731935af08bee6eaef7e497d22f8dd670d30c5e40deb07413 -size 52580 diff --git a/Content/Toasts/Tests/FT_ToastsToastCreation.ts b/Content/Toasts/Tests/FT_ToastsToastCreation.ts deleted file mode 100644 index da43053..0000000 --- a/Content/Toasts/Tests/FT_ToastsToastCreation.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Toasts/Tests/FT_ToastsToastCreation.ts - -import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; -import { UEArray } from '#root/UE/UEArray.ts'; -import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; - -/** - * Functional Test: Toast Creation by Message Type - * Validates toast creation functionality for all message types - * Tests that each type (Info, Success, Warning, Error, Debug) creates valid toasts - */ -export class FT_ToastsToastCreation extends FunctionalTest { - // ════════════════════════════════════════════════════════════════════════════════════════ - // GRAPHS - // ════════════════════════════════════════════════════════════════════════════════════════ - - // ──────────────────────────────────────────────────────────────────────────────────────── - // EventGraph - // ──────────────────────────────────────────────────────────────────────────────────────── - - /** - * Test execution - validates toast creation for each message type - * Iterates through all message types and verifies successful creation - */ - EventStartTest(): void { - this.ToastComponent.InitializeToastSystem(); - - this.ToastTypes.forEach(arrayElement => { - // Create toast for current message type and check if valid ID is returned - if ( - this.ToastComponent.ShowToast( - `Test ${arrayElement} message`, - arrayElement, - 1 - ) <= 0 - ) { - this.FinishTest( - EFunctionalTestResult.Failed, - `Failed to create ${arrayElement} toast` - ); - } - }); - - this.FinishTest(EFunctionalTestResult.Succeeded); - } - - // ════════════════════════════════════════════════════════════════════════════════════════ - // VARIABLES - // ════════════════════════════════════════════════════════════════════════════════════════ - - /** - * Toast notification system - component under test - * @category Components - */ - private ToastComponent = new AC_ToastSystem(); - - /** - * Array of all message types to test - * Covers complete range of supported toast types - * @category Test Data - */ - private ToastTypes: UEArray = new UEArray([ - E_MessageType.Info, - E_MessageType.Success, - E_MessageType.Warning, - E_MessageType.Error, - E_MessageType.Debug, - ]); -} diff --git a/Content/Toasts/Tests/FT_ToastsToastCreation.uasset b/Content/Toasts/Tests/FT_ToastsToastCreation.uasset deleted file mode 100644 index a840b91..0000000 --- a/Content/Toasts/Tests/FT_ToastsToastCreation.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:235e9fb054eeac04851daaf80ed1ed55260fd5e16cd02dcea17edb54e52a513d -size 71995 diff --git a/Content/Toasts/UI/WBP_Toast.ts b/Content/Toasts/UI/WBP_Toast.ts index b01e9ce..1add500 100644 --- a/Content/Toasts/UI/WBP_Toast.ts +++ b/Content/Toasts/UI/WBP_Toast.ts @@ -1,13 +1,13 @@ -// Toasts/UI/WBP_Toast.ts +// Content/Toasts/UI/WBP_Toast.ts -import { Border } from '#root/UE/Border.ts'; -import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import type { Text } from '#root/UE/Text.ts'; -import { TextBlock } from '#root/UE/TextBlock.ts'; -import { UserWidget } from '#root/UE/UserWidget.ts'; -import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; -import { BFL_Colors } from '#root/UI/Libraries/BFL_Colors.ts'; +import { Border } from '/Content/UE/Border.ts'; +import { MathLibrary } from '/Content/UE/MathLibrary.ts'; +import { SystemLibrary } from '/Content/UE/SystemLibrary.ts'; +import type { Text } from '/Content/UE/Text.ts'; +import { TextBlock } from '/Content/UE/TextBlock.ts'; +import { UserWidget } from '/Content/UE/UserWidget.ts'; +import { E_MessageType } from '/Content/UI/Enums/E_MessageType.ts'; +import { BFL_Colors } from '/Content/UI/Libraries/BFL_Colors.ts'; /** * Individual Toast Notification Widget diff --git a/Content/Toasts/UI/WBP_ToastContainer.ts b/Content/Toasts/UI/WBP_ToastContainer.ts index 2e89eb1..c842075 100644 --- a/Content/Toasts/UI/WBP_ToastContainer.ts +++ b/Content/Toasts/UI/WBP_ToastContainer.ts @@ -1,14 +1,14 @@ -// Toasts/UI/WBP_ToastContainer.ts +// Content/Toasts/UI/WBP_ToastContainer.ts -import { WBP_Toast } from '#root/Toasts/UI/WBP_Toast.ts'; -import { CreateWidget } from '#root/UE/CteateWidget.ts'; -import { ESlateVisibility } from '#root/UE/ESlateVisibility.ts'; -import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; -import type { Text } from '#root/UE/Text.ts'; -import { UEArray } from '#root/UE/UEArray.ts'; -import { UserWidget } from '#root/UE/UserWidget.ts'; -import { VerticalBox } from '#root/UE/VerticalBox.ts'; -import type { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; +import { WBP_Toast } from '/Content/Toasts/UI/WBP_Toast.ts'; +import { CreateWidget } from '/Content/UE/CteateWidget.ts'; +import { ESlateVisibility } from '/Content/UE/ESlateVisibility.ts'; +import { SystemLibrary } from '/Content/UE/SystemLibrary.ts'; +import type { Text } from '/Content/UE/Text.ts'; +import { UEArray } from '/Content/UE/UEArray.ts'; +import { UserWidget } from '/Content/UE/UserWidget.ts'; +import { VerticalBox } from '/Content/UE/VerticalBox.ts'; +import type { E_MessageType } from '/Content/UI/Enums/E_MessageType.ts'; /** * Toast Container Widget diff --git a/Content/UE/Actor.ts b/Content/UE/Actor.ts index 5d36be0..fd8e3f4 100644 --- a/Content/UE/Actor.ts +++ b/Content/UE/Actor.ts @@ -1,9 +1,9 @@ -// UE/Actor.ts +// Content/UE/Actor.ts -import { Name } from '#root/UE/Name.ts'; -import { Rotator } from '#root/UE/Rotator.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; -import { Vector } from '#root/UE/Vector.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { Rotator } from '/Content/UE/Rotator.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; +import { Vector } from '/Content/UE/Vector.ts'; export class Actor extends UEObject { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/ActorComponent.ts b/Content/UE/ActorComponent.ts index 78a6863..ddf64d5 100644 --- a/Content/UE/ActorComponent.ts +++ b/Content/UE/ActorComponent.ts @@ -1,8 +1,8 @@ -// UE/ActorComponent.ts +// Content/UE/ActorComponent.ts -import { Actor } from '#root/UE/Actor.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Actor } from '/Content/UE/Actor.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class ActorComponent extends UEObject { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/BitmaskInteger.ts b/Content/UE/BitmaskInteger.ts index 2336b8a..c58cd95 100644 --- a/Content/UE/BitmaskInteger.ts +++ b/Content/UE/BitmaskInteger.ts @@ -1,3 +1,3 @@ -// UE/BitmaskInteger.ts +// Content/UE/BitmaskInteger.ts export type BitmaskInteger = number; diff --git a/Content/UE/BlueprintFunctionLibrary.ts b/Content/UE/BlueprintFunctionLibrary.ts index de111b3..a5c2805 100644 --- a/Content/UE/BlueprintFunctionLibrary.ts +++ b/Content/UE/BlueprintFunctionLibrary.ts @@ -1,6 +1,6 @@ -// UE/BlueprintFunctionLibrary.ts +// Content/UE/BlueprintFunctionLibrary.ts -import { UEObject } from '#root/UE/UEObject.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class BlueprintFunctionLibrary extends UEObject { constructor( diff --git a/Content/UE/Border.ts b/Content/UE/Border.ts index 157adef..2ee7d72 100644 --- a/Content/UE/Border.ts +++ b/Content/UE/Border.ts @@ -1,9 +1,9 @@ -// UE/Border.ts +// Content/UE/Border.ts -import { ContentWidget } from '#root/UE/ContentWidget.ts'; -import type { LinearColor } from '#root/UE/LinearColor.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { ContentWidget } from '/Content/UE/ContentWidget.ts'; +import type { LinearColor } from '/Content/UE/LinearColor.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class Border extends ContentWidget { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/Byte.ts b/Content/UE/Byte.ts index fac1342..6abcffc 100644 --- a/Content/UE/Byte.ts +++ b/Content/UE/Byte.ts @@ -1,3 +1,3 @@ -// UE/Byte.ts +// Content/UE/Byte.ts export type Byte = number; diff --git a/Content/UE/CapsuleComponent.ts b/Content/UE/CapsuleComponent.ts index f61c995..d1de73a 100644 --- a/Content/UE/CapsuleComponent.ts +++ b/Content/UE/CapsuleComponent.ts @@ -1,5 +1,7 @@ -import type { Float } from '#root/UE/Float.ts'; -import { ShapeComponent } from '#root/UE/ShapeComponent.ts'; +// Content/UE/CapsuleComponen.ts + +import type { Float } from '/Content/UE/Float.ts'; +import { ShapeComponent } from '/Content/UE/ShapeComponent.ts'; export class CapsuleComponent extends ShapeComponent { constructor(outer: ShapeComponent | null = null, name: string = 'None') { diff --git a/Content/UE/Cast.ts b/Content/UE/Cast.ts index a238692..e0c1ce5 100644 --- a/Content/UE/Cast.ts +++ b/Content/UE/Cast.ts @@ -1,4 +1,4 @@ -// Content/Cast.ts +// Content/UE/Cast.ts export function Cast(obj: unknown): T | null { return (obj as T) || null; diff --git a/Content/UE/Color.ts b/Content/UE/Color.ts index a0c2343..902e284 100644 --- a/Content/UE/Color.ts +++ b/Content/UE/Color.ts @@ -1,7 +1,7 @@ -// UE/Color.ts +// Content/UE/Color.ts -import { StructBase } from '#root/UE/StructBase.ts'; -import type { UInt8 } from '#root/UE/UInt8.ts'; +import { StructBase } from '/Content/UE/StructBase.ts'; +import type { UInt8 } from '/Content/UE/UInt8.ts'; export class Color extends StructBase { constructor( diff --git a/Content/UE/ContentWidget.ts b/Content/UE/ContentWidget.ts index 7b92cba..4a34ef5 100644 --- a/Content/UE/ContentWidget.ts +++ b/Content/UE/ContentWidget.ts @@ -1,8 +1,8 @@ -// UE/ContentWidget.ts +// Content/UE/ContentWidget.ts -import { Name } from '#root/UE/Name.ts'; -import { PanelWidget } from '#root/UE/PanelWidget.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { PanelWidget } from '/Content/UE/PanelWidget.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class ContentWidget extends PanelWidget { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/Controller.ts b/Content/UE/Controller.ts index 2764dc6..0af6d19 100644 --- a/Content/UE/Controller.ts +++ b/Content/UE/Controller.ts @@ -1,9 +1,9 @@ -// UE/Controller.ts +// Content/UE/Controller.ts -import { Actor } from '#root/UE/Actor.ts'; -import { Name } from '#root/UE/Name.ts'; -import type { Rotator } from '#root/UE/Rotator.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Actor } from '/Content/UE/Actor.ts'; +import { Name } from '/Content/UE/Name.ts'; +import type { Rotator } from '/Content/UE/Rotator.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class Controller extends Actor { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/CteateWidget.ts b/Content/UE/CteateWidget.ts index 7f05d04..7c7c793 100644 --- a/Content/UE/CteateWidget.ts +++ b/Content/UE/CteateWidget.ts @@ -1,6 +1,6 @@ -// UE/CreateWidget.ts +// Content/UE/CreateWidget.ts -import type { UserWidget } from '#root/UE/UserWidget.ts'; +import type { UserWidget } from '/Content/UE/UserWidget.ts'; type WidgetConstructor = new () => T; diff --git a/Content/UE/DataAsset.ts b/Content/UE/DataAsset.ts index 4f1087a..33d763e 100644 --- a/Content/UE/DataAsset.ts +++ b/Content/UE/DataAsset.ts @@ -1,7 +1,7 @@ -// UE/DataAsset.ts +// Content/UE/DataAsset.ts -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class DataAsset extends UEObject { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/DataTable.ts b/Content/UE/DataTable.ts deleted file mode 100644 index 413b366..0000000 --- a/Content/UE/DataTable.ts +++ /dev/null @@ -1,41 +0,0 @@ -// UE/DataTable.ts - -import { Name } from '#root/UE/Name.ts'; -import type { UEArray } from '#root/UE/UEArray.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; - -export class DataTable extends UEObject { - private rows: Map = new Map(); - - constructor( - outer: UEObject | null = null, - name: Name = Name.NONE, - initialData: UEArray = [] as unknown as UEArray< - T & { Name: Name } - > - ) { - super(outer, name); - - initialData.forEach(row => { - this.rows.set(row.Name, row); - }); - } - - public GetDataTableRow( - rowName: Name, - rowFound?: (row: T) => void, - rowNotFound?: () => void - ): void | T | undefined { - const row = this.rows.get(rowName); - - if (!rowFound && !rowNotFound) { - return row; - } - - if (row) { - rowFound?.(row); - } else { - rowNotFound?.(); - } - } -} diff --git a/Content/UE/DataTableFunctionLibrary.ts b/Content/UE/DataTableFunctionLibrary.ts deleted file mode 100644 index 48e3789..0000000 --- a/Content/UE/DataTableFunctionLibrary.ts +++ /dev/null @@ -1,20 +0,0 @@ -// UE/DataTableFunctionLibrary.ts - -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import type { DataTable } from '#root/UE/DataTable.ts'; -import { Name } from '#root/UE/Name.ts'; - -class DataTableFunctionLibraryClass extends BlueprintFunctionLibrary { - constructor( - outer: null | BlueprintFunctionLibrary = null, - name: string = 'DataTableFunctionLibrary' - ) { - super(outer, name); - } - - public GetDataTableRowNames(table: DataTable): Name[] { - return Array.from(table['rows'].keys()); - } -} - -export const DataTableFunctionLibrary = new DataTableFunctionLibraryClass(); diff --git a/Content/UE/DynamicSubsystem.ts b/Content/UE/DynamicSubsystem.ts index 83a5756..ccc83c6 100644 --- a/Content/UE/DynamicSubsystem.ts +++ b/Content/UE/DynamicSubsystem.ts @@ -1,8 +1,8 @@ -// UE/DynamicSubsystem.ts +// Content/UE/DynamicSubsystem.ts -import { Name } from '#root/UE/Name.ts'; -import { Subsystem } from '#root/UE/Subsystem.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { Subsystem } from '/Content/UE/Subsystem.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class DynamicSubsystem extends Subsystem { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/EDrawDebugTrace.ts b/Content/UE/EDrawDebugTrace.ts deleted file mode 100644 index 200f27d..0000000 --- a/Content/UE/EDrawDebugTrace.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum EDrawDebugTrace { - None = 'None', - ForOneFrame = 'ForOneFrame', - ForDuration = 'ForDuration', - Persistent = 'Persistent', -} diff --git a/Content/UE/EFunctionalTestResult.ts b/Content/UE/EFunctionalTestResult.ts index b014837..e2eaa52 100644 --- a/Content/UE/EFunctionalTestResult.ts +++ b/Content/UE/EFunctionalTestResult.ts @@ -1,4 +1,4 @@ -// UE/EFunctionalTestResult.ts +// Content/UE/EFunctionalTestResult.ts export enum EFunctionalTestResult { 'Default' = 'Default', diff --git a/Content/UE/EHardwareDevicePrimaryType.ts b/Content/UE/EHardwareDevicePrimaryType.ts index dec2c3e..7dcf7f1 100644 --- a/Content/UE/EHardwareDevicePrimaryType.ts +++ b/Content/UE/EHardwareDevicePrimaryType.ts @@ -1,4 +1,4 @@ -// UE/EHardwareDevicePrimaryType.ts +// Content/UE/EHardwareDevicePrimaryType.ts export enum EHardwareDevicePrimaryType { Unspecified = 'Unspecified', diff --git a/Content/UE/ESlateVisibility.ts b/Content/UE/ESlateVisibility.ts index 5b3d21f..7ca9cb7 100644 --- a/Content/UE/ESlateVisibility.ts +++ b/Content/UE/ESlateVisibility.ts @@ -1,4 +1,4 @@ -// UE/ESlateVisibility.ts +// Content/UE/ESlateVisibility.ts export enum ESlateVisibility { Visible = 'Visible', diff --git a/Content/UE/ETraceTypeQuery.ts b/Content/UE/ETraceTypeQuery.ts deleted file mode 100644 index 664835b..0000000 --- a/Content/UE/ETraceTypeQuery.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum ETraceTypeQuery { - Visibility = 'Visibility', - Camera = 'Camera', -} diff --git a/Content/UE/EngineSubsystem.ts b/Content/UE/EngineSubsystem.ts index 95403b1..ff3aa37 100644 --- a/Content/UE/EngineSubsystem.ts +++ b/Content/UE/EngineSubsystem.ts @@ -1,8 +1,8 @@ -// UE/EngineSubsystem.ts +// Content/UE/EngineSubsystem.ts -import { DynamicSubsystem } from '#root/UE/DynamicSubsystem.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { DynamicSubsystem } from '/Content/UE/DynamicSubsystem.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class EngineSubsystem extends DynamicSubsystem { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/EnhancedActionKeyMapping.ts b/Content/UE/EnhancedActionKeyMapping.ts index fb762a3..b961e0b 100644 --- a/Content/UE/EnhancedActionKeyMapping.ts +++ b/Content/UE/EnhancedActionKeyMapping.ts @@ -1,11 +1,11 @@ -// UE/EnhancedActionKeyMapping.ts +// Content/UE/EnhancedActionKeyMapping.ts -import type { InputAction } from '#root/UE/InputAction.ts'; -import type { InputModifier } from '#root/UE/InputModifier.ts'; -import type { InputTrigger } from '#root/UE/InputTrigger.ts'; -import type { Key } from '#root/UE/Key.ts'; -import { StructBase } from '#root/UE/StructBase.ts'; -import type { UEArray } from '#root/UE/UEArray.ts'; +import type { InputAction } from '/Content/UE/InputAction.ts'; +import type { InputModifier } from '/Content/UE/InputModifier.ts'; +import type { InputTrigger } from '/Content/UE/InputTrigger.ts'; +import type { Key } from '/Content/UE/Key.ts'; +import { StructBase } from '/Content/UE/StructBase.ts'; +import type { UEArray } from '/Content/UE/UEArray.ts'; export class EnhancedActionKeyMapping extends StructBase { public action: InputAction; diff --git a/Content/UE/EnhancedInputLocalPlayerSubsystem.ts b/Content/UE/EnhancedInputLocalPlayerSubsystem.ts index 3eba426..7d30c3b 100644 --- a/Content/UE/EnhancedInputLocalPlayerSubsystem.ts +++ b/Content/UE/EnhancedInputLocalPlayerSubsystem.ts @@ -1,11 +1,11 @@ -// UE/EnhancedInputLocalPlayerSubsystem.ts +// Content/UE/EnhancedInputLocalPlayerSubsystem.ts -import type { InputMappingContext } from '#root/UE/InputMappingContext.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { LocalPlayerSubsystem } from '#root/UE/LocalPlayerSubsystem.ts'; -import { ModifyContextOptions } from '#root/UE/ModifyContextOptions.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import type { InputMappingContext } from '/Content/UE/InputMappingContext.ts'; +import type { Integer } from '/Content/UE/Integer.ts'; +import { LocalPlayerSubsystem } from '/Content/UE/LocalPlayerSubsystem.ts'; +import { ModifyContextOptions } from '/Content/UE/ModifyContextOptions.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class EnhancedInputLocalPlayerSubsystem extends LocalPlayerSubsystem { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/Float.ts b/Content/UE/Float.ts index 826c3af..6723ffd 100644 --- a/Content/UE/Float.ts +++ b/Content/UE/Float.ts @@ -1,3 +1,3 @@ -// UE/Float.ts +// Content/UE/Float.ts export type Float = number; diff --git a/Content/UE/FunctionalTest.ts b/Content/UE/FunctionalTest.ts deleted file mode 100644 index 54b6d6e..0000000 --- a/Content/UE/FunctionalTest.ts +++ /dev/null @@ -1,21 +0,0 @@ -// UE/FunctionalTest.ts - -import { Actor } from '#root/UE/Actor.ts'; -import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; - -export class FunctionalTest extends Actor { - constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { - super(outer, name); - } - - public FinishTest( - testResult: EFunctionalTestResult = EFunctionalTestResult.Default, - message: string = '' - ): void { - console.log( - `Test Finished with result: ${testResult}. Message: ${message}` - ); - } -} diff --git a/Content/UE/GameModeBase.ts b/Content/UE/GameModeBase.ts index f83081b..e08bf4a 100644 --- a/Content/UE/GameModeBase.ts +++ b/Content/UE/GameModeBase.ts @@ -1,8 +1,8 @@ -// Content/GameModeBase.ts +// Content/UE/GameModeBase.ts -import { Info } from '#root/UE/Info.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Info } from '/Content/UE/Info.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class GameModeBase extends Info { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/HardwareDeviceIdentifier.ts b/Content/UE/HardwareDeviceIdentifier.ts index 28b7e72..6d407a0 100644 --- a/Content/UE/HardwareDeviceIdentifier.ts +++ b/Content/UE/HardwareDeviceIdentifier.ts @@ -1,9 +1,9 @@ -// UE/HardwareDeviceIdentifier.ts +// Content/UE/HardwareDeviceIdentifier.ts -import type { BitmaskInteger } from '#root/UE/BitmaskInteger.ts'; -import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts'; -import { Name } from '#root/UE/Name.ts'; -import { StructBase } from '#root/UE/StructBase.ts'; +import type { BitmaskInteger } from '/Content/UE/BitmaskInteger.ts'; +import { EHardwareDevicePrimaryType } from '/Content/UE/EHardwareDevicePrimaryType.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { StructBase } from '/Content/UE/StructBase.ts'; export class HardwareDeviceIdentifier extends StructBase { public InputClassName: Name; diff --git a/Content/UE/HitResult.ts b/Content/UE/HitResult.ts deleted file mode 100644 index 992a859..0000000 --- a/Content/UE/HitResult.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { Actor } from '#root/UE/Actor.ts'; -import type { Float } from '#root/UE/Float.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { Name } from '#root/UE/Name.ts'; -import type { PhysicalMaterial } from '#root/UE/PhysicalMaterial.ts'; -import type { PrimitiveComponent } from '#root/UE/PrimitiveComponent.ts'; -import { StructBase } from '#root/UE/StructBase.ts'; -import { Vector } from '#root/UE/Vector.ts'; - -export class HitResult extends StructBase { - BlockingHit: boolean; - InitialOverlap: boolean; - Time: Float; - Distance: Float; - Location: Vector; - ImpactPoint: Vector; - Normal: Vector; - ImpactNormal: Vector; - PhysMat: PhysicalMaterial; - HitActor: Actor; - HitComponent: PrimitiveComponent; - HitBoneName: Name; - BoneName: Name; - HitItem: Integer; - ElementIndex: Integer; - FaceIndex: Integer; - TraceStart: Vector; - TraceEnd: Vector; - - constructor( - BlockingHit: boolean = false, - InitialOverlap: boolean = false, - Time: number = 0.0, - Distance: number = 0.0, - Location: Vector = new Vector(), - ImpactPoint: Vector = new Vector(), - Normal: Vector = new Vector(), - ImpactNormal: Vector = new Vector(), - PhysMat?: PhysicalMaterial, - HitActor?: Actor, - HitComponent?: PrimitiveComponent, - HitBoneName: Name = new Name('None'), - BoneName: Name = new Name('None'), - HitItem: number = 0, - ElementIndex: number = 0, - FaceIndex: number = 0, - TraceStart: Vector = new Vector(), - TraceEnd: Vector = new Vector() - ) { - super(); - - this.BlockingHit = BlockingHit; - this.InitialOverlap = InitialOverlap; - this.Time = Time; - this.Distance = Distance; - this.Location = Location; - this.ImpactPoint = ImpactPoint; - this.Normal = Normal; - this.ImpactNormal = ImpactNormal; - this.PhysMat = PhysMat!; - this.HitActor = HitActor!; - this.HitComponent = HitComponent!; - this.HitBoneName = HitBoneName; - this.BoneName = BoneName; - this.HitItem = HitItem; - this.ElementIndex = ElementIndex; - this.FaceIndex = FaceIndex; - this.TraceStart = TraceStart; - this.TraceEnd = TraceEnd; - } -} diff --git a/Content/UE/Info.ts b/Content/UE/Info.ts index bb5b9ee..8d0384b 100644 --- a/Content/UE/Info.ts +++ b/Content/UE/Info.ts @@ -1,8 +1,8 @@ -// UE/Info.ts +// Content/UE/Info.ts -import { Actor } from '#root/UE/Actor.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Actor } from '/Content/UE/Actor.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class Info extends Actor { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/InputAction.ts b/Content/UE/InputAction.ts index b5ec03a..5567e24 100644 --- a/Content/UE/InputAction.ts +++ b/Content/UE/InputAction.ts @@ -1,8 +1,8 @@ -// UE/InputAction.ts +// Content/UE/InputAction.ts -import { DataAsset } from '#root/UE/DataAsset.ts'; -import { Name } from '#root/UE/Name.ts'; -import type { UEObject } from '#root/UE/UEObject.ts'; +import { DataAsset } from '/Content/UE/DataAsset.ts'; +import { Name } from '/Content/UE/Name.ts'; +import type { UEObject } from '/Content/UE/UEObject.ts'; export class InputAction extends DataAsset { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/InputDeviceSubsystem.ts b/Content/UE/InputDeviceSubsystem.ts index 9719009..5fddb42 100644 --- a/Content/UE/InputDeviceSubsystem.ts +++ b/Content/UE/InputDeviceSubsystem.ts @@ -1,11 +1,11 @@ -// UE/InputDeviceSubsystem.ts +// Content/UE/InputDeviceSubsystem.ts -import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts'; -import { EngineSubsystem } from '#root/UE/EngineSubsystem.ts'; -import { HardwareDeviceIdentifier } from '#root/UE/HardwareDeviceIdentifier.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { EHardwareDevicePrimaryType } from '/Content/UE/EHardwareDevicePrimaryType.ts'; +import { EngineSubsystem } from '/Content/UE/EngineSubsystem.ts'; +import { HardwareDeviceIdentifier } from '/Content/UE/HardwareDeviceIdentifier.ts'; +import type { Integer } from '/Content/UE/Integer.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; class InputDeviceSubsystemClass extends EngineSubsystem { private readonly currentDevice: HardwareDeviceIdentifier; diff --git a/Content/UE/InputMappingContext.ts b/Content/UE/InputMappingContext.ts index 684aba6..c99cfbd 100644 --- a/Content/UE/InputMappingContext.ts +++ b/Content/UE/InputMappingContext.ts @@ -1,14 +1,14 @@ -// UE/InputMappingContext.ts +// Content/UE/InputMappingContext.ts -import { DataAsset } from '#root/UE/DataAsset.ts'; -import { EnhancedActionKeyMapping } from '#root/UE/EnhancedActionKeyMapping.ts'; -import type { InputAction } from '#root/UE/InputAction.ts'; -import type { InputModifier } from '#root/UE/InputModifier.ts'; -import type { InputTrigger } from '#root/UE/InputTrigger.ts'; -import type { Key } from '#root/UE/Key.ts'; -import { Name } from '#root/UE/Name.ts'; -import type { UEArray } from '#root/UE/UEArray.ts'; -import type { UEObject } from '#root/UE/UEObject.ts'; +import { DataAsset } from '/Content/UE/DataAsset.ts'; +import { EnhancedActionKeyMapping } from '/Content/UE/EnhancedActionKeyMapping.ts'; +import type { InputAction } from '/Content/UE/InputAction.ts'; +import type { InputModifier } from '/Content/UE/InputModifier.ts'; +import type { InputTrigger } from '/Content/UE/InputTrigger.ts'; +import type { Key } from '/Content/UE/Key.ts'; +import { Name } from '/Content/UE/Name.ts'; +import type { UEArray } from '/Content/UE/UEArray.ts'; +import type { UEObject } from '/Content/UE/UEObject.ts'; export class InputMappingContext extends DataAsset { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/InputModifier.ts b/Content/UE/InputModifier.ts index d99f732..fb3d5c1 100644 --- a/Content/UE/InputModifier.ts +++ b/Content/UE/InputModifier.ts @@ -1,7 +1,7 @@ -// UE/InputModifier.ts +// Content/UE/InputModifier.ts -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class InputModifier extends UEObject { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/InputTrigger.ts b/Content/UE/InputTrigger.ts index ff4173b..325694b 100644 --- a/Content/UE/InputTrigger.ts +++ b/Content/UE/InputTrigger.ts @@ -1,7 +1,7 @@ -// UE/InputTrigger.ts +// Content/UE/InputTrigger.ts -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class InputTrigger extends UEObject { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/Integer.ts b/Content/UE/Integer.ts index d408e67..05e9e9b 100644 --- a/Content/UE/Integer.ts +++ b/Content/UE/Integer.ts @@ -1,3 +1,3 @@ -// UE/Integer.ts +// Content/UE/Integer.ts export type Integer = number; diff --git a/Content/UE/Key.ts b/Content/UE/Key.ts index 2ce2acd..c4c91d6 100644 --- a/Content/UE/Key.ts +++ b/Content/UE/Key.ts @@ -1,7 +1,7 @@ // Content/UE/Key.ts -import { Name } from '#root/UE/Name.ts'; -import { StructBase } from '#root/UE/StructBase.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { StructBase } from '/Content/UE/StructBase.ts'; export class Key extends StructBase { public keyName: Name; diff --git a/Content/UE/LinearColor.ts b/Content/UE/LinearColor.ts index fc662c3..40b250e 100644 --- a/Content/UE/LinearColor.ts +++ b/Content/UE/LinearColor.ts @@ -1,7 +1,7 @@ -// UE/LinearColor.ts +// Content/UE/LinearColor.ts -import type { Float } from '#root/UE/Float.ts'; -import { StructBase } from '#root/UE/StructBase.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import { StructBase } from '/Content/UE/StructBase.ts'; export class LinearColor extends StructBase { public A: Float = 0; diff --git a/Content/UE/LocalPlayerSubsystem.ts b/Content/UE/LocalPlayerSubsystem.ts index 15ee963..1f1b129 100644 --- a/Content/UE/LocalPlayerSubsystem.ts +++ b/Content/UE/LocalPlayerSubsystem.ts @@ -1,8 +1,8 @@ -// UE/LocalPlayerSubsystem.ts +// Content/UE/LocalPlayerSubsystem.ts -import { Name } from '#root/UE/Name.ts'; -import { Subsystem } from '#root/UE/Subsystem.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { Subsystem } from '/Content/UE/Subsystem.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class LocalPlayerSubsystem extends Subsystem { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/MathLibrary.ts b/Content/UE/MathLibrary.ts index 330c9b9..dc88f66 100644 --- a/Content/UE/MathLibrary.ts +++ b/Content/UE/MathLibrary.ts @@ -1,11 +1,10 @@ -// UE/SystemLibrary.ts +// Content/UE/SystemLibrary.ts -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import type { Color } from '#root/UE/Color.ts'; -import type { Float } from '#root/UE/Float.ts'; -import type { Integer } from '#root/UE/Integer.ts'; -import { LinearColor } from '#root/UE/LinearColor.ts'; -import { Vector } from '#root/UE/Vector.ts'; +import { BlueprintFunctionLibrary } from '/Content/UE/BlueprintFunctionLibrary.ts'; +import type { Color } from '/Content/UE/Color.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import { LinearColor } from '/Content/UE/LinearColor.ts'; +import { Vector } from '/Content/UE/Vector.ts'; /** * System Library: Core Mathematical Functions @@ -24,30 +23,6 @@ class MathLibraryClass extends BlueprintFunctionLibrary { // FUNCTIONS // ════════════════════════════════════════════════════════════════════════════════════════ - /** - * Convert degrees to radians - * @param Degrees - Angle in degrees - * @returns Angle in radians - * @example - * // Convert 90 degrees to radians - * DegreesToRadians(90) // returns π/2 - */ - public DegreesToRadians(Degrees: Float): Float { - return (Degrees * Math.PI) / 180; - } - - /** - * Convert radians to degrees - * @param Radians - Angle in radians - * @returns Angle in degrees - * @example - * // Convert π/2 radians to degrees - * RadiansToDegrees(Math.PI / 2) // returns 90 - */ - public RadiansToDegrees(Radians: Float): Float { - return (Radians * 180) / Math.PI; - } - /** * Calculate sine of angle in radians * @param Value - Angle in radians @@ -66,50 +41,6 @@ class MathLibraryClass extends BlueprintFunctionLibrary { return Math.cos(Value); } - /** - * Calculate arccosine (inverse cosine) of value - * @param Value - Input value (-1 to 1) - * @returns Angle in radians (0 to π) - */ - public Acos(Value: Float): Float { - return Math.acos(Value); - } - - /** - * Calculate arctangent2 of Y and X in radians - * @param Y - Y coordinate - * @param X - X coordinate - * @returns Angle in radians (-π to π) - */ - public Atan2Radians(Y: Float, X: Float): Float { - return Math.atan2(Y, X); - } - - /** - * Calculate arctangent2 of Y and X in degrees - * @param Y - Y coordinate - * @param X - X coordinate - * @returns Angle in degrees (-180 to 180) - */ - public Atan2Degrees(Y: Float, X: Float): Float { - return this.RadiansToDegrees(Math.atan2(Y, X)); - } - - /** - * Calculate dot product of two vectors - * @param Vector1 - First vector - * @param Vector2 - Second vector - * @returns Dot product scalar value - * @example - * // Dot product of perpendicular vectors - * Dot(new Vector(1,0,0), new Vector(0,1,0)) // returns 0 - */ - public Dot(Vector1: Vector, Vector2: Vector): Float { - return ( - Vector1.X * Vector2.X + Vector1.Y * Vector2.Y + Vector1.Z * Vector2.Z - ); - } - /** * Color to LinearColor conversion * @param color - Color with 0-255 RGBA components @@ -183,77 +114,6 @@ class MathLibraryClass extends BlueprintFunctionLibrary { return Current + DeltaMove; } - /** - * Absolute value of a float - * @param Value - Input value - * @returns Absolute value - * @example - * // Absolute value of -5 - * abs(-5) // returns 5 - */ - public abs(Value: Float): Float { - return Math.abs(Value); - } - - /** - * Interpolate a vector towards a target vector - * @param Current - Current vector - * @param Target - Target vector - * @param DeltaTime - Time since last update - * @param InterpSpeed - Speed of interpolation - * @returns New interpolated vector - * @example - * // Interpolate (0,0,0) towards (10,10,10) over 1 second at speed 5 - * VInterpTo(new Vector(0,0,0), new Vector(10,10,10), 1, 5) // returns Vector(5,5,5) - */ - public VInterpTo( - Current: Vector = new Vector(0, 0, 0), - Target: Vector = new Vector(0, 0, 0), - DeltaTime: Float = 0, - InterpSpeed: Float = 0 - ): Vector { - return new Vector( - this.FInterpTo(Current.X, Target.X, DeltaTime, InterpSpeed), - this.FInterpTo(Current.Y, Target.Y, DeltaTime, InterpSpeed), - this.FInterpTo(Current.Z, Target.Z, DeltaTime, InterpSpeed) - ); - } - - /** - * Normalize a vector to unit length - * @param A - Input vector - * @param Tolerance - Minimum length to avoid division by zero - * @returns Normalized vector (length 1) or zero vector if input is zero - * @example - * // Normalize vector (3,4,0) - * Normalize(new Vector(3,4,0)) // returns Vector(0.6,0.8,0) - */ - public Normal( - A: Vector = new Vector(0, 0, 0), - Tolerance: Float = 0.0001 - ): Vector { - const length = this.VectorLength(A); - - if (length > Tolerance) { - return new Vector(A.X / length, A.Y / length, A.Z / length); - } - - return new Vector(0, 0, 0); - } - - /** - * Get the minimum of two float values - * @param A - First value - * @param B - Second value - * @returns Minimum value - * @example - * // Minimum of 3 and 5 - * Min(3, 5) // returns 3 - */ - public Min(A: Float, B: Float): Float { - return Math.min(A, B); - } - /** * Get right vector from roll, pitch, yaw angles * @param Roll - Rotation around forward axis in radians @@ -303,18 +163,6 @@ class MathLibraryClass extends BlueprintFunctionLibrary { return new Vector(CP * CY, CP * SY, SP); } - - /** - * Floor a float value to the nearest lower integer - * @param Value - Input float value - * @returns Floored integer value - * @example - * // Floor 3.7 to 3 - * Floor(3.7) // returns 3 - */ - public Ceil(Value: Float): Integer { - return Math.ceil(Value); - } } /** diff --git a/Content/UE/ModifyContextOptions.ts b/Content/UE/ModifyContextOptions.ts index 1db80ed..6954b1b 100644 --- a/Content/UE/ModifyContextOptions.ts +++ b/Content/UE/ModifyContextOptions.ts @@ -1,6 +1,6 @@ -// UE/ModifyContextOptions.ts +// Content/UE/ModifyContextOptions.ts -import { StructBase } from '#root/UE/StructBase.ts'; +import { StructBase } from '/Content/UE/StructBase.ts'; export class ModifyContextOptions extends StructBase { public forceImmediately: boolean = false; diff --git a/Content/UE/Name.ts b/Content/UE/Name.ts index 6b648c7..d35e862 100644 --- a/Content/UE/Name.ts +++ b/Content/UE/Name.ts @@ -1,6 +1,6 @@ -// UE/Name.ts +// Content/UE/Name.ts -import { _WrapperBase } from '#root/UE/_WrapperBase.ts'; +import { _WrapperBase } from '/Content/UE/_WrapperBase.ts'; export class Name extends _WrapperBase { private readonly value: string; diff --git a/Content/UE/PanelSlot.ts b/Content/UE/PanelSlot.ts index 3713dc4..c049a82 100644 --- a/Content/UE/PanelSlot.ts +++ b/Content/UE/PanelSlot.ts @@ -1,8 +1,8 @@ -// UE/PanelSlot.ts +// Content/UE/PanelSlot.ts -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; -import { Visual } from '#root/UE/Visual.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; +import { Visual } from '/Content/UE/Visual.ts'; export class PanelSlot extends Visual { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/PanelWidget.ts b/Content/UE/PanelWidget.ts index 40b8f62..e84347a 100644 --- a/Content/UE/PanelWidget.ts +++ b/Content/UE/PanelWidget.ts @@ -1,9 +1,9 @@ -// UE/PanelWidget.ts +// Content/UE/PanelWidget.ts -import { Name } from '#root/UE/Name.ts'; -import { PanelSlot } from '#root/UE/PanelSlot.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; -import { Widget } from '#root/UE/Widget.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { PanelSlot } from '/Content/UE/PanelSlot.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; +import { Widget } from '/Content/UE/Widget.ts'; export class PanelWidget extends Widget { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/Pawn.ts b/Content/UE/Pawn.ts index 364965d..dd4a8b9 100644 --- a/Content/UE/Pawn.ts +++ b/Content/UE/Pawn.ts @@ -1,10 +1,10 @@ -// UE/Pawn.ts +// Content/UE/Pawn.ts -import { Actor } from '#root/UE/Actor.ts'; -import { Controller } from '#root/UE/Controller.ts'; -import { Name } from '#root/UE/Name.ts'; -import { Rotator } from '#root/UE/Rotator.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Actor } from '/Content/UE/Actor.ts'; +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'; export class Pawn extends Actor { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/PhysicalMaterial.ts b/Content/UE/PhysicalMaterial.ts deleted file mode 100644 index 554850c..0000000 --- a/Content/UE/PhysicalMaterial.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; - -export class PhysicalMaterial extends UEObject { - constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { - super(outer, name); - } -} diff --git a/Content/UE/PlayerController.ts b/Content/UE/PlayerController.ts index 93a8229..a85b839 100644 --- a/Content/UE/PlayerController.ts +++ b/Content/UE/PlayerController.ts @@ -1,8 +1,8 @@ -// UE/PlayerController.ts +// Content/UE/PlayerController.ts -import { Controller } from '#root/UE/Controller.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Controller } from '/Content/UE/Controller.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class PlayerController extends Controller { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/PrimaryDataAsset.ts b/Content/UE/PrimaryDataAsset.ts index 5058bf3..a00cc01 100644 --- a/Content/UE/PrimaryDataAsset.ts +++ b/Content/UE/PrimaryDataAsset.ts @@ -1,8 +1,8 @@ -// UE/PrimaryDataAsset.ts +// Content/UE/PrimaryDataAsset.ts -import { DataAsset } from '#root/UE/DataAsset.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { DataAsset } from '/Content/UE/DataAsset.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class PrimaryDataAsset extends DataAsset { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/PrimitiveComponent.ts b/Content/UE/PrimitiveComponent.ts index d1da72e..1bba44c 100644 --- a/Content/UE/PrimitiveComponent.ts +++ b/Content/UE/PrimitiveComponent.ts @@ -1,4 +1,6 @@ -import { SceneComponent } from '#root/UE/SceneComponent.ts'; +// Content/UE/PrimitiveComponent.ts + +import { SceneComponent } from '/Content/UE/SceneComponent.ts'; export class PrimitiveComponent extends SceneComponent { constructor(outer: SceneComponent | null = null, name: string = 'None') { diff --git a/Content/UE/Rotator.ts b/Content/UE/Rotator.ts index efa46fa..a19d79b 100644 --- a/Content/UE/Rotator.ts +++ b/Content/UE/Rotator.ts @@ -1,7 +1,7 @@ -// UE/Rotator.ts +// Content/UE/Rotator.ts -import type { Float } from '#root/UE/Float.ts'; -import { StructBase } from '#root/UE/StructBase.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import { StructBase } from '/Content/UE/StructBase.ts'; export class Rotator extends StructBase { constructor( diff --git a/Content/UE/SceneComponent.ts b/Content/UE/SceneComponent.ts index 8e2f186..5caf69d 100644 --- a/Content/UE/SceneComponent.ts +++ b/Content/UE/SceneComponent.ts @@ -1,6 +1,8 @@ -import { ActorComponent } from '#root/UE/ActorComponent.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +// Content/UE/SceneComponent.ts + +import { ActorComponent } from '/Content/UE/ActorComponent.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class SceneComponent extends ActorComponent { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/ShapeComponent.ts b/Content/UE/ShapeComponent.ts index cd7579f..f6d63cb 100644 --- a/Content/UE/ShapeComponent.ts +++ b/Content/UE/ShapeComponent.ts @@ -1,4 +1,6 @@ -import { PrimitiveComponent } from '#root/UE/PrimitiveComponent.ts'; +// Content/UE/ShapeComponent.ts + +import { PrimitiveComponent } from '/Content/UE/PrimitiveComponent.ts'; export class ShapeComponent extends PrimitiveComponent { constructor(outer: PrimitiveComponent | null = null, name: string = 'None') { diff --git a/Content/UE/StringLibrary.ts b/Content/UE/StringLibrary.ts deleted file mode 100644 index 29848d2..0000000 --- a/Content/UE/StringLibrary.ts +++ /dev/null @@ -1,23 +0,0 @@ -// UE/StringLibrary.ts - -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import type { Vector } from '#root/UE/Vector.ts'; - -class StringLibraryClass extends BlueprintFunctionLibrary { - constructor( - outer: null | BlueprintFunctionLibrary = null, - name: string = 'StringLibrary' - ) { - super(outer, name); - } - - public Append(A: string, B: string): string { - return A + B; - } - - public ConvVectorToString(InVector: Vector): string { - return `X=${InVector.X}, Y=${InVector.Y}, Z=${InVector.Z}`; - } -} - -export const StringLibrary = new StringLibraryClass(); diff --git a/Content/UE/StructBase.ts b/Content/UE/StructBase.ts index 39fb8d4..91c2f2d 100644 --- a/Content/UE/StructBase.ts +++ b/Content/UE/StructBase.ts @@ -1,8 +1,8 @@ -// UE/StructBase.ts +// Content/UE/StructBase.ts -import { _WrapperBase } from '#root/UE/_WrapperBase.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { _WrapperBase } from '/Content/UE/_WrapperBase.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class StructBase extends _WrapperBase { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/Subsystem.ts b/Content/UE/Subsystem.ts index 94cb308..97aff71 100644 --- a/Content/UE/Subsystem.ts +++ b/Content/UE/Subsystem.ts @@ -1,7 +1,7 @@ -// UE/Subsystem.ts +// Content/UE/Subsystem.ts -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class Subsystem extends UEObject { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/SystemLibrary.ts b/Content/UE/SystemLibrary.ts index 1ce26a7..091e51d 100644 --- a/Content/UE/SystemLibrary.ts +++ b/Content/UE/SystemLibrary.ts @@ -1,15 +1,10 @@ -// UE/SystemLibrary.ts +// Content/UE/SystemLibrary.ts -import type { Actor } from '#root/UE/Actor.ts'; -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import { EDrawDebugTrace } from '#root/UE/EDrawDebugTrace.ts'; -import { ETraceTypeQuery } from '#root/UE/ETraceTypeQuery.ts'; -import type { Float } from '#root/UE/Float.ts'; -import { HitResult } from '#root/UE/HitResult.ts'; -import { LinearColor } from '#root/UE/LinearColor.ts'; -import { Name } from '#root/UE/Name.ts'; -import type { UEObject } from '#root/UE/UEObject.ts'; -import { Vector } from '#root/UE/Vector.ts'; +import { BlueprintFunctionLibrary } from '/Content/UE/BlueprintFunctionLibrary.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import { LinearColor } from '/Content/UE/LinearColor.ts'; +import { Name } from '/Content/UE/Name.ts'; +import type { UEObject } from '/Content/UE/UEObject.ts'; class SystemLibraryClass extends BlueprintFunctionLibrary { constructor( @@ -55,76 +50,6 @@ class SystemLibraryClass extends BlueprintFunctionLibrary { console.log(duration); console.log(key); } - - public CapsuleTraceByChannel( - Start: Vector = new Vector(0, 0, 0), - End: Vector = new Vector(0, 0, 0), - Radius: Float = 0.0, - HalfHeight: Float = 0.0, - TraceChannel: ETraceTypeQuery = ETraceTypeQuery.Visibility, - TraceComplex: boolean = false, - ActorsToIgnore: Actor[] = [], - DrawDebugType: EDrawDebugTrace = EDrawDebugTrace.None, - IgnoreSelf: boolean = true, - TraceColor: LinearColor = new LinearColor(1, 0, 0, 1), - TraceHitColor: LinearColor = new LinearColor(0, 1, 0, 1), - DrawTime: Float = 5.0 - ): { OutHit: HitResult; ReturnValue: boolean } { - console.log('CapsuleTraceByChannel called with:', { - Start, - End, - Radius, - HalfHeight, - TraceChannel, - TraceComplex, - ActorsToIgnore, - DrawDebugType, - IgnoreSelf, - TraceColor, - TraceHitColor, - DrawTime, - }); - - return { - OutHit: new HitResult(), - ReturnValue: false, - }; - - // Placeholder implementation; replace with actual trace logic - } - - public LineTraceByChannel( - Start: Vector = new Vector(0, 0, 0), - End: Vector = new Vector(0, 0, 0), - TraceChannel: ETraceTypeQuery = ETraceTypeQuery.Visibility, - TraceComplex: boolean = false, - ActorsToIgnore: Actor[] = [], - DrawDebugType: EDrawDebugTrace = EDrawDebugTrace.None, - IgnoreSelf: boolean = true, - TraceColor: LinearColor = new LinearColor(1, 0, 0, 1), - TraceHitColor: LinearColor = new LinearColor(0, 1, 0, 1), - DrawTime: Float = 5.0 - ): { OutHit: HitResult; ReturnValue: boolean } { - console.log('CapsuleTraceByChannel called with:', { - Start, - End, - TraceChannel, - TraceComplex, - ActorsToIgnore, - DrawDebugType, - IgnoreSelf, - TraceColor, - TraceHitColor, - DrawTime, - }); - - return { - OutHit: new HitResult(), - ReturnValue: false, - }; - - // Placeholder implementation; replace with actual trace logic - } } export const SystemLibrary = new SystemLibraryClass(); diff --git a/Content/UE/Text.ts b/Content/UE/Text.ts index d6ecc68..03b9d69 100644 --- a/Content/UE/Text.ts +++ b/Content/UE/Text.ts @@ -1,3 +1,3 @@ -// UE/Text.ts +// Content/UE/Text.ts export type Text = string; diff --git a/Content/UE/TextBlock.ts b/Content/UE/TextBlock.ts index 8b3d26c..a03ecf8 100644 --- a/Content/UE/TextBlock.ts +++ b/Content/UE/TextBlock.ts @@ -1,9 +1,9 @@ -// UE/TextBlock.ts +// Content/UE/TextBlock.ts -import { Name } from '#root/UE/Name.ts'; -import type { Text } from '#root/UE/Text.ts'; -import { TextLayoutWidget } from '#root/UE/TextLayoutWidget.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import type { Text } from '/Content/UE/Text.ts'; +import { TextLayoutWidget } from '/Content/UE/TextLayoutWidget.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class TextBlock extends TextLayoutWidget { private text: Text = '' as Text; diff --git a/Content/UE/TextLayoutWidget.ts b/Content/UE/TextLayoutWidget.ts index 2d0e140..fc2c309 100644 --- a/Content/UE/TextLayoutWidget.ts +++ b/Content/UE/TextLayoutWidget.ts @@ -1,8 +1,8 @@ -// UE/TextLayoutWidget.ts +// Content/UE/TextLayoutWidget.ts -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; -import { Widget } from '#root/UE/Widget.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; +import { Widget } from '/Content/UE/Widget.ts'; export class TextLayoutWidget extends Widget { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/TextLibrary.ts b/Content/UE/TextLibrary.ts index b1fd106..c858ca9 100644 --- a/Content/UE/TextLibrary.ts +++ b/Content/UE/TextLibrary.ts @@ -1,7 +1,7 @@ -// UE/TextLibrary.ts +// Content/UE/TextLibrary.ts -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import type { Text } from '#root/UE/Text.ts'; +import { BlueprintFunctionLibrary } from '/Content/UE/BlueprintFunctionLibrary.ts'; +import type { Text } from '/Content/UE/Text.ts'; class TextLibraryClass extends BlueprintFunctionLibrary { constructor( diff --git a/Content/UE/UEArray.ts b/Content/UE/UEArray.ts index de37d12..fdc87ed 100644 --- a/Content/UE/UEArray.ts +++ b/Content/UE/UEArray.ts @@ -1,6 +1,6 @@ -// UE/UEArray.ts +// Content/UE/UEArray.ts -import type { Integer } from '#root/UE/Integer.ts'; +import type { Integer } from '/Content/UE/Integer.ts'; export class UEArray extends Array { constructor(items?: T[]) { diff --git a/Content/UE/UEObject.ts b/Content/UE/UEObject.ts index ae7460b..5f456ff 100644 --- a/Content/UE/UEObject.ts +++ b/Content/UE/UEObject.ts @@ -1,6 +1,6 @@ -// UE/UEObject.ts +// Content/UE/UEObject.ts -import { Name } from '#root/UE/Name.ts'; +import { Name } from '/Content/UE/Name.ts'; export class UEObject { protected outer: UEObject | null; diff --git a/Content/UE/UInt8.ts b/Content/UE/UInt8.ts index 7f865fb..a63acf5 100644 --- a/Content/UE/UInt8.ts +++ b/Content/UE/UInt8.ts @@ -1,3 +1,3 @@ -// UE/UInt8.ts +// Content/UE/UInt8.ts export type UInt8 = number; diff --git a/Content/UE/UserWidget.ts b/Content/UE/UserWidget.ts index d412e8f..7e53172 100644 --- a/Content/UE/UserWidget.ts +++ b/Content/UE/UserWidget.ts @@ -1,8 +1,8 @@ -// UE/UserWidget.ts +// Content/UE/UserWidget.ts -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; -import { Widget } from '#root/UE/Widget.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; +import { Widget } from '/Content/UE/Widget.ts'; export class UserWidget extends Widget { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/Vector.ts b/Content/UE/Vector.ts index 1918475..ee9fe7b 100644 --- a/Content/UE/Vector.ts +++ b/Content/UE/Vector.ts @@ -1,8 +1,8 @@ -// UE/Vector.ts +// Content/UE/Vector.ts -import type { Float } from '#root/UE/Float.ts'; -import { Name } from '#root/UE/Name.ts'; -import { StructBase } from '#root/UE/StructBase.ts'; +import type { Float } from '/Content/UE/Float.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { StructBase } from '/Content/UE/StructBase.ts'; export class Vector extends StructBase { public X: Float = 0; diff --git a/Content/UE/VerticalBox.ts b/Content/UE/VerticalBox.ts index f584c8c..e0e64bd 100644 --- a/Content/UE/VerticalBox.ts +++ b/Content/UE/VerticalBox.ts @@ -1,8 +1,8 @@ -// UE/VerticalBox.ts +// Content/UE/VerticalBox.ts -import { Name } from '#root/UE/Name.ts'; -import { PanelWidget } from '#root/UE/PanelWidget.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { PanelWidget } from '/Content/UE/PanelWidget.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class VerticalBox extends PanelWidget { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/Visual.ts b/Content/UE/Visual.ts index 401bcd4..7c1aad3 100644 --- a/Content/UE/Visual.ts +++ b/Content/UE/Visual.ts @@ -1,7 +1,7 @@ -// UE/Visual.ts +// Content/UE/Visual.ts -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class Visual extends UEObject { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/Widget.ts b/Content/UE/Widget.ts index 447a38e..442d1ec 100644 --- a/Content/UE/Widget.ts +++ b/Content/UE/Widget.ts @@ -1,9 +1,9 @@ -// UE/Widget.ts +// Content/UE/Widget.ts -import type { ESlateVisibility } from '#root/UE/ESlateVisibility.ts'; -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; -import { Visual } from '#root/UE/Visual.ts'; +import type { ESlateVisibility } from '/Content/UE/ESlateVisibility.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; +import { Visual } from '/Content/UE/Visual.ts'; export class Widget extends Visual { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UE/_WrapperBase.ts b/Content/UE/_WrapperBase.ts index dffef30..150f8e7 100644 --- a/Content/UE/_WrapperBase.ts +++ b/Content/UE/_WrapperBase.ts @@ -1,7 +1,7 @@ -// UE/_WrapperBase.ts +// Content/UE/_WrapperBase.ts -import { Name } from '#root/UE/Name.ts'; -import { UEObject } from '#root/UE/UEObject.ts'; +import { Name } from '/Content/UE/Name.ts'; +import { UEObject } from '/Content/UE/UEObject.ts'; export class _WrapperBase extends UEObject { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { diff --git a/Content/UI/Enums/E_MessageType.ts b/Content/UI/Enums/E_MessageType.ts index c442387..b165b75 100644 --- a/Content/UI/Enums/E_MessageType.ts +++ b/Content/UI/Enums/E_MessageType.ts @@ -1,4 +1,4 @@ -// UI/Enums/E_MessageType.ts +// Content/UI/Enums/E_MessageType.ts export enum E_MessageType { Info = 'Info', diff --git a/Content/UI/Libraries/BFL_Colors.ts b/Content/UI/Libraries/BFL_Colors.ts index 5d5f098..04a42b4 100644 --- a/Content/UI/Libraries/BFL_Colors.ts +++ b/Content/UI/Libraries/BFL_Colors.ts @@ -1,9 +1,9 @@ -// UI/Libraries/BFL_Colors.ts +// Content/UI/Libraries/BFL_Colors.ts -import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; -import type { Byte } from '#root/UE/Byte.ts'; -import { Color } from '#root/UE/Color.ts'; -import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts'; +import { BlueprintFunctionLibrary } from '/Content/UE/BlueprintFunctionLibrary.ts'; +import type { Byte } from '/Content/UE/Byte.ts'; +import { Color } from '/Content/UE/Color.ts'; +import { E_MessageType } from '/Content/UI/Enums/E_MessageType.ts'; class BFL_ColorsClass extends BlueprintFunctionLibrary { public GetColorByMessageType(Type: E_MessageType, Alpha: Byte): Color { diff --git a/Source/TengriPlatformer.Target.cs b/Source/TengriPlatformer.Target.cs new file mode 100644 index 0000000..171c452 --- /dev/null +++ b/Source/TengriPlatformer.Target.cs @@ -0,0 +1,15 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class TengriPlatformerTarget : TargetRules +{ + public TengriPlatformerTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + DefaultBuildSettings = BuildSettingsVersion.V5; + + ExtraModuleNames.AddRange( new string[] { "TengriPlatformer" } ); + } +} diff --git a/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp b/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp new file mode 100644 index 0000000..d2db6bf --- /dev/null +++ b/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp @@ -0,0 +1,402 @@ +// Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp + +#include "TengriCollisionResolver.h" +#include "Components/CapsuleComponent.h" +#include "Engine/World.h" + +// ════════════════════════════════════════════════════════════════════════════ +// CONSTANTS +// ════════════════════════════════════════════════════════════════════════════ + +namespace TengriPhysics +{ + // Contact offset to prevent surface penetration + constexpr float ContactOffset = 0.005f; + + // Minimum movement distance (below this, stop iterating) + constexpr float MinMoveDist = 0.001f; + + // Step-up: reject surfaces sloping toward us more than this + constexpr float StepUpMaxSlopeZ = 0.2f; + + // Step-up: reject overhangs/ceilings + constexpr float StepUpMinNormalZ = -0.01f; + + // Minimum upward velocity to count as "jumping" + constexpr float MinUpwardVelocity = 0.01f; + + // Minimum Normal.Z to be considered ground in step-down + constexpr float MinGroundNormalZ = 0.1f; + + // Step height tolerance for final validation + constexpr float StepHeightTolerance = 1.0f; + + // Overlap check uses slightly smaller capsule + constexpr float OverlapCheckScale = 0.95f; + + // Down trace extends slightly beyond step height + constexpr float DownTraceMultiplier = 1.2f; +} + +// ════════════════════════════════════════════════════════════════════════════ +// PRIMITIVES +// ════════════════════════════════════════════════════════════════════════════ + +FTengriSweepResult UTengriCollisionResolver::PerformSweep( + const UObject* WorldContext, + const FVector& Start, + const FVector& End, + const UCapsuleComponent* Capsule, + bool bShowDebug) +{ + FTengriSweepResult Result; + Result.Location = End; + Result.bBlocked = false; + + if (!WorldContext || !Capsule) + { + return Result; + } + + UWorld* World = WorldContext->GetWorld(); + if (!World) + { + return Result; + } + + FCollisionQueryParams Params; + Params.AddIgnoredActor(Capsule->GetOwner()); + Params.bTraceComplex = false; + + const bool bHit = World->SweepSingleByChannel( + Result.Hit, + Start, + End, + Capsule->GetComponentQuat(), + ECC_Visibility, + FCollisionShape::MakeCapsule( + Capsule->GetScaledCapsuleRadius(), + Capsule->GetScaledCapsuleHalfHeight() + ), + Params + ); + + if (bHit) + { + Result.bBlocked = true; + Result.Location = Result.Hit.Location; + } + + return Result; +} + +FVector UTengriCollisionResolver::ClipVelocity(const FVector& Velocity, const FVector& Normal) +{ + const float Backoff = FVector::DotProduct(Velocity, Normal); + if (Backoff > 0.f) + { + return Velocity; + } + return Velocity - (Normal * Backoff); +} + +FVector UTengriCollisionResolver::ProjectVelocity(const FVector& Velocity, const FVector& Normal) +{ + const FVector Dir = ClipVelocity(Velocity, Normal).GetSafeNormal(); + return Dir * Velocity.Size(); +} + +// ════════════════════════════════════════════════════════════════════════════ +// STEP UP +// ════════════════════════════════════════════════════════════════════════════ + +bool UTengriCollisionResolver::StepUp( + const UObject* WorldContext, + const FVector& StartLocation, + const FVector& DesiredDelta, + const FHitResult& ImpactHit, + const UCapsuleComponent* Capsule, + float MaxStepHeight, + FVector& OutLocation) +{ + // Reject sloped surfaces and overhangs + if (ImpactHit.ImpactNormal.Z > TengriPhysics::StepUpMaxSlopeZ || + ImpactHit.ImpactNormal.Z < TengriPhysics::StepUpMinNormalZ) + { + return false; + } + + // Reject if falling + if (DesiredDelta.Z < TengriPhysics::StepUpMinNormalZ) + { + return false; + } + + // === Phase A: Trace Up === + const FVector StepUpEnd = StartLocation + FVector(0.f, 0.f, MaxStepHeight); + const FTengriSweepResult UpSweep = PerformSweep(WorldContext, StartLocation, StepUpEnd, Capsule, false); + + // Reject if not enough headroom (hit ceiling before half step height) + if (UpSweep.bBlocked && UpSweep.Location.Z < (StartLocation.Z + MaxStepHeight * 0.5f)) + { + return false; + } + + // === Phase B: Trace Forward === + // When pressing against a wall, DesiredDelta approaches zero. + // We must check forward at least CapsuleRadius to avoid phasing through geometry. + FVector ForwardDir = DesiredDelta.GetSafeNormal2D(); + if (ForwardDir.IsNearlyZero()) + { + ForwardDir = -ImpactHit.ImpactNormal; + } + + const float MinCheckDist = Capsule ? Capsule->GetScaledCapsuleRadius() : 30.0f; + const float CheckDist = FMath::Max(DesiredDelta.Size2D(), MinCheckDist); + const FVector ForwardEnd = UpSweep.Location + (ForwardDir * CheckDist); + + const FTengriSweepResult ForwardSweep = PerformSweep(WorldContext, UpSweep.Location, ForwardEnd, Capsule, false); + + // Reject if obstacle continues upward (wall, next stair step) + if (ForwardSweep.bBlocked) + { + return false; + } + + // === Phase C: Trace Down === + const FVector DownStart = ForwardSweep.Location; + const FVector DownEnd = DownStart - FVector(0.f, 0.f, MaxStepHeight * TengriPhysics::DownTraceMultiplier); + const FTengriSweepResult DownSweep = PerformSweep(WorldContext, DownStart, DownEnd, Capsule, false); + + if (!DownSweep.bBlocked || DownSweep.Hit.ImpactNormal.Z < TengriPhysics::MinGroundNormalZ) + { + return false; + } + + // Validate actual height difference + const float RealHeightDiff = DownSweep.Location.Z - StartLocation.Z; + if (RealHeightDiff > (MaxStepHeight + TengriPhysics::StepHeightTolerance)) + { + return false; + } + + // === Phase D: Overlap Check === + // Verify we can stand at the new location without intersecting geometry + FCollisionQueryParams OverlapParams; + OverlapParams.AddIgnoredActor(Capsule->GetOwner()); + + const bool bOverlap = WorldContext->GetWorld()->OverlapBlockingTestByChannel( + DownSweep.Location, + Capsule->GetComponentQuat(), + ECC_Visibility, + FCollisionShape::MakeCapsule( + Capsule->GetScaledCapsuleRadius() * TengriPhysics::OverlapCheckScale, + Capsule->GetScaledCapsuleHalfHeight() * TengriPhysics::OverlapCheckScale + ), + OverlapParams + ); + + if (bOverlap) + { + return false; + } + + OutLocation = DownSweep.Location; + return true; +} + +// ════════════════════════════════════════════════════════════════════════════ +// GROUND SNAPPING +// ════════════════════════════════════════════════════════════════════════════ + +bool UTengriCollisionResolver::SnapToGround( + const UObject* WorldContext, + const UCapsuleComponent* Capsule, + const float SnapDistance, + const FSurfaceThresholds& Thresholds, + FVector& OutLocation, + FHitResult& OutHit) +{ + if (!WorldContext || !Capsule) + { + return false; + } + + const FVector Start = Capsule->GetComponentLocation(); + const FVector End = Start - FVector(0.f, 0.f, SnapDistance); + + if (const FTengriSweepResult Sweep = PerformSweep(WorldContext, Start, End, Capsule, false); Sweep.bBlocked && Thresholds.IsWalkable(Sweep.Hit.ImpactNormal.Z)) + { + OutLocation = Sweep.Location; + OutHit = Sweep.Hit; + return true; + } + + return false; +} + +// ════════════════════════════════════════════════════════════════════════════ +// SLIDE HELPERS +// ════════════════════════════════════════════════════════════════════════════ + +namespace +{ + /** Calculate horizontal slide along overhang/ceiling when walking (not jumping) */ + FVector CalculateOverhangSlide(const FVector& RemainingDelta, const FVector& ImpactNormal) + { + // If jumping into ceiling, use normal clip + if (RemainingDelta.Z > TengriPhysics::MinUpwardVelocity) + { + return UTengriCollisionResolver::ClipVelocity(RemainingDelta, ImpactNormal); + } + + // Walking under sloped ceiling - slide horizontally only + const FVector UpVector(0.f, 0.f, 1.f); + FVector HorizontalTangent = FVector::CrossProduct(ImpactNormal, UpVector).GetSafeNormal(); + + if (HorizontalTangent.IsNearlyZero()) + { + return FVector::ZeroVector; + } + + // Align tangent with desired movement direction + if (FVector::DotProduct(HorizontalTangent, RemainingDelta) < 0.f) + { + HorizontalTangent *= -1.f; + } + + return HorizontalTangent * FVector::DotProduct(RemainingDelta, HorizontalTangent); + } + + /** Calculate slide along wall, preventing upward velocity from steep surfaces */ + FVector CalculateWallSlide(const FVector& RemainingDelta, const FVector& ImpactNormal) + { + FVector ClipDelta = UTengriCollisionResolver::ClipVelocity(RemainingDelta, ImpactNormal); + + // If wall would push us up, force horizontal movement only + if (ClipDelta.Z > 0.f) + { + FVector HorizontalTangent = FVector::CrossProduct(ImpactNormal, FVector(0.f, 0.f, 1.f)).GetSafeNormal(); + + if (!HorizontalTangent.IsNearlyZero()) + { + if (FVector::DotProduct(HorizontalTangent, RemainingDelta) < 0.f) + { + HorizontalTangent *= -1.f; + } + return HorizontalTangent * FVector::DotProduct(RemainingDelta, HorizontalTangent); + } + + ClipDelta.Z = 0.f; + } + + return ClipDelta; + } + + /** Apply crease logic when stuck between two surfaces */ + FVector ApplyCreaseLogic( + const FVector& NewDelta, + const FVector& RemainingDelta, + const FVector& PrevNormal, + const FVector& CurrentNormal) + { + if (const FVector Crease = FVector::CrossProduct(PrevNormal, CurrentNormal); Crease.SizeSquared() > TengriPhysics::ContactOffset) + { + const float Dot = FVector::DotProduct(RemainingDelta, Crease); + return Crease * Dot; + } + + return NewDelta; + } +} + +// ════════════════════════════════════════════════════════════════════════════ +// MAIN RESOLUTION +// ════════════════════════════════════════════════════════════════════════════ + +FTengriSweepResult UTengriCollisionResolver::ResolveMovement( + const UObject* WorldContext, + const FVector& StartLocation, + const FVector& DesiredDelta, + const UCapsuleComponent* Capsule, + const FSurfaceThresholds& Thresholds, + const float MaxStepHeight, + const int32 MaxIterations, + const bool bShowDebug) +{ + FTengriSweepResult FinalResult; + FinalResult.Location = StartLocation; + + FVector CurrentLocation = StartLocation; + FVector RemainingDelta = DesiredDelta; + FVector PrevNormal = FVector::ZeroVector; + + for (int32 Iteration = 0; Iteration < MaxIterations; ++Iteration) + { + const FVector Target = CurrentLocation + RemainingDelta; + const FTengriSweepResult Sweep = PerformSweep(WorldContext, CurrentLocation, Target, Capsule, bShowDebug); + + FinalResult.CollisionCount++; + + // No collision - movement complete + if (!Sweep.bBlocked) + { + CurrentLocation = Sweep.Location; + break; + } + + FinalResult.bBlocked = true; + FinalResult.Hit = Sweep.Hit; + + // Classify surface + const float NormalZ = Sweep.Hit.ImpactNormal.Z; + const bool bIsFloor = Thresholds.IsWalkable(NormalZ); + const bool bIsOverhang = Thresholds.IsOverhang(NormalZ); + + // Try step-up for walls + if (!bIsFloor && !bIsOverhang) + { + if (FVector StepDest; StepUp(WorldContext, CurrentLocation, RemainingDelta, Sweep.Hit, Capsule, MaxStepHeight, StepDest)) + { + CurrentLocation = StepDest; + break; + } + } + + // Apply contact offset to prevent penetration + CurrentLocation = Sweep.Location + (Sweep.Hit.ImpactNormal * TengriPhysics::ContactOffset); + + // Calculate slide direction based on surface type + FVector NewDelta; + if (bIsFloor) + { + NewDelta = ProjectVelocity(RemainingDelta, Sweep.Hit.ImpactNormal); + } + else if (bIsOverhang) + { + NewDelta = CalculateOverhangSlide(RemainingDelta, Sweep.Hit.ImpactNormal); + } + else + { + NewDelta = CalculateWallSlide(RemainingDelta, Sweep.Hit.ImpactNormal); + } + + // Handle corner/crease situations + if (Iteration > 0) + { + NewDelta = ApplyCreaseLogic(NewDelta, RemainingDelta, PrevNormal, Sweep.Hit.ImpactNormal); + } + + PrevNormal = Sweep.Hit.ImpactNormal; + RemainingDelta = NewDelta; + + // Stop if remaining movement is negligible + if (RemainingDelta.SizeSquared() < FMath::Square(TengriPhysics::MinMoveDist)) + { + break; + } + } + + FinalResult.Location = CurrentLocation; + return FinalResult; +} \ No newline at end of file diff --git a/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h b/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h new file mode 100644 index 0000000..b9e632b --- /dev/null +++ b/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h @@ -0,0 +1,93 @@ +// Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "TengriPlatformer/Movement/Core/TengriMovementConfig.h" +#include "TengriPlatformer/Movement/Collision/TengriSweepResult.h" +#include "TengriCollisionResolver.generated.h" + +class UCapsuleComponent; + +/** + * Static collision resolution utilities for Tengri Movement System. + * Handles swept collision, wall sliding, step-up, and ground snapping. + */ +UCLASS() +class TENGRIPLATFORMER_API UTengriCollisionResolver : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + // ════════════════════════════════════════════════════════════════════ + // MAIN RESOLUTION + // ════════════════════════════════════════════════════════════════════ + + /** + * Resolve movement with collision: slide along walls, step up obstacles. + * @param WorldContext - Object providing world context + * @param StartLocation - Starting position + * @param DesiredDelta - Desired movement this frame + * @param Capsule - Collision capsule + * @param Thresholds - Surface classification thresholds + * @param MaxStepHeight - Maximum step-up height + * @param MaxIterations - Maximum slide iterations + * @param bShowDebug - Enable debug visualization + * @return Final position and collision info + */ + static FTengriSweepResult ResolveMovement( + const UObject* WorldContext, + const FVector& StartLocation, + const FVector& DesiredDelta, + const UCapsuleComponent* Capsule, + const FSurfaceThresholds& Thresholds, + float MaxStepHeight, + int32 MaxIterations, + bool bShowDebug = false + ); + + // ════════════════════════════════════════════════════════════════════ + // PRIMITIVES + // ════════════════════════════════════════════════════════════════════ + + /** Perform a simple capsule sweep from Start to End */ + static FTengriSweepResult PerformSweep( + const UObject* WorldContext, + const FVector& Start, + const FVector& End, + const UCapsuleComponent* Capsule, + bool bShowDebug = false + ); + + /** Attempt to step up over an obstacle */ + static bool StepUp( + const UObject* WorldContext, + const FVector& StartLocation, + const FVector& DesiredDelta, + const FHitResult& ImpactHit, + const UCapsuleComponent* Capsule, + float MaxStepHeight, + FVector& OutLocation + ); + + /** Snap character to ground surface below */ + static bool SnapToGround( + const UObject* WorldContext, + const UCapsuleComponent* Capsule, + float SnapDistance, + const FSurfaceThresholds& Thresholds, + FVector& OutLocation, + FHitResult& OutHit + ); + + // ════════════════════════════════════════════════════════════════════ + // VELOCITY MATH + // ════════════════════════════════════════════════════════════════════ + + /** Remove velocity component going into the surface */ + static FVector ClipVelocity(const FVector& Velocity, const FVector& Normal); + + /** Project velocity along surface, preserving magnitude */ + static FVector ProjectVelocity(const FVector& Velocity, const FVector& Normal); +}; \ No newline at end of file diff --git a/Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h b/Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h new file mode 100644 index 0000000..0f2570c --- /dev/null +++ b/Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h @@ -0,0 +1,32 @@ +// Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h + +#pragma once + +#include "CoreMinimal.h" +#include "TengriSweepResult.generated.h" + +/** + * Result of a capsule sweep operation. + * Contains final location, hit information, and collision metadata. + */ +USTRUCT(BlueprintType) +struct TENGRIPLATFORMER_API FTengriSweepResult +{ + GENERATED_BODY() + + /** Final location after sweep (either End or hit location) */ + UPROPERTY(BlueprintReadOnly) + FVector Location = FVector::ZeroVector; + + /** Hit result if collision occurred */ + UPROPERTY(BlueprintReadOnly) + FHitResult Hit; + + /** True if sweep was blocked by collision */ + UPROPERTY(BlueprintReadOnly) + bool bBlocked = false; + + /** Number of collision iterations performed */ + UPROPERTY(BlueprintReadOnly) + int32 CollisionCount = 0; +}; \ No newline at end of file diff --git a/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp new file mode 100644 index 0000000..27e7ce6 --- /dev/null +++ b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp @@ -0,0 +1,46 @@ +// Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp + +#include "TengriMovementConfig.h" + +FSurfaceThresholds UTengriMovementConfig::GetThresholds() const +{ + FSurfaceThresholds Out; + Out.WalkableZ = FMath::Cos(FMath::DegreesToRadians(WalkableAngleDeg)); + Out.SteepSlopeZ = FMath::Cos(FMath::DegreesToRadians(SteepSlopeAngleDeg)); + Out.WallZ = FMath::Cos(FMath::DegreesToRadians(WallAngleDeg)); + return Out; +} + +#if WITH_EDITOR +void UTengriMovementConfig::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + // Validate angle hierarchy + if (WalkableAngleDeg >= SteepSlopeAngleDeg) + { + UE_LOG(LogTemp, Warning, + TEXT("TengriMovementConfig: WalkableAngle (%.1f) should be less than SteepSlopeAngle (%.1f)"), + WalkableAngleDeg, SteepSlopeAngleDeg); + } + + if (SteepSlopeAngleDeg >= WallAngleDeg) + { + UE_LOG(LogTemp, Warning, + TEXT("TengriMovementConfig: SteepSlopeAngle (%.1f) should be less than WallAngle (%.1f)"), + SteepSlopeAngleDeg, WallAngleDeg); + } + + if (MaxStepHeight <= 0.f) + { + UE_LOG(LogTemp, Warning, + TEXT("TengriMovementConfig: MaxStepHeight should be positive")); + } + + if (GroundSnapDistance <= 0.f) + { + UE_LOG(LogTemp, Warning, + TEXT("TengriMovementConfig: GroundSnapDistance should be positive")); + } +} +#endif \ No newline at end of file diff --git a/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h new file mode 100644 index 0000000..6080fa3 --- /dev/null +++ b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h @@ -0,0 +1,157 @@ +// Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "TengriMovementConfig.generated.h" + +// ============================================================================ +// SURFACE THRESHOLDS +// ============================================================================ + +/** + * Pre-calculated cosine thresholds for surface classification. + * Generated from angle degrees in UTengriMovementConfig. + */ +USTRUCT(BlueprintType) +struct TENGRIPLATFORMER_API FSurfaceThresholds +{ + GENERATED_BODY() + + /** Minimum Normal.Z for walkable floor (cos of max walkable angle) */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Thresholds") + float WalkableZ = 0.64f; + + /** Minimum Normal.Z for steep slopes */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Thresholds") + float SteepSlopeZ = 0.08f; + + /** Minimum Normal.Z for walls (below = ceiling/overhang) */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Thresholds") + float WallZ = -0.08f; + + // === Classification Helpers === + + FORCEINLINE bool IsWalkable(const float NormalZ) const + { + return NormalZ >= WalkableZ; + } + + FORCEINLINE bool IsSteepSlope(const float NormalZ) const + { + return NormalZ >= SteepSlopeZ && NormalZ < WalkableZ; + } + + FORCEINLINE bool IsWall(const float NormalZ) const + { + return NormalZ >= WallZ && NormalZ < SteepSlopeZ; + } + + FORCEINLINE bool IsOverhang(const float NormalZ) const + { + return NormalZ < WallZ; + } +}; + +// ============================================================================ +// MOVEMENT CONFIG +// ============================================================================ + +/** + * Data asset containing all movement system parameters. + * Create instances in Content Browser for different character types. + */ +UCLASS(BlueprintType) +class TENGRIPLATFORMER_API UTengriMovementConfig : public UDataAsset +{ + GENERATED_BODY() + +public: + // ======================================================================== + // PHYSICS + // ======================================================================== + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") + float MaxSpeed = 800.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") + float Acceleration = 2048.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") + float Friction = 8.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") + float Gravity = 980.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") + float RotationSpeed = 360.0f; + + /** Minimum speed (cm/s) required to rotate character toward movement */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") + float MinSpeedForRotation = 10.0f; + + // ======================================================================== + // SURFACE ANGLES + // ======================================================================== + + /** Maximum angle (degrees) that counts as walkable floor */ + UPROPERTY(EditAnywhere, Category = "Surface Angles", meta = (ClampMin = "0", ClampMax = "89")) + float WalkableAngleDeg = 50.0f; + + /** Maximum angle for steep slopes (limited movement) */ + UPROPERTY(EditAnywhere, Category = "Surface Angles", meta = (ClampMin = "0", ClampMax = "89")) + float SteepSlopeAngleDeg = 85.0f; + + /** Maximum angle for walls (above = ceiling) */ + UPROPERTY(EditAnywhere, Category = "Surface Angles", meta = (ClampMin = "90", ClampMax = "180")) + float WallAngleDeg = 95.0f; + + // ======================================================================== + // COLLISION + // ======================================================================== + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision") + float CapsuleRadius = 34.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision") + float CapsuleHalfHeight = 88.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision") + int32 MaxSlideIterations = 3; + + /** Maximum height of step that can be walked over */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision") + float MaxStepHeight = 45.0f; + + // ======================================================================== + // GROUND SNAPPING + // ======================================================================== + + /** Distance to search for ground when snapping */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground Snapping") + float GroundSnapDistance = 20.0f; + + /** Micro-offset above ground to prevent floor penetration */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground Snapping") + float GroundSnapOffset = 0.15f; + + // ======================================================================== + // SLOPE PHYSICS + // ======================================================================== + + /** Velocity damping on steep slopes (0 = full stop, 1 = full slide) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics", meta = (ClampMin = "0", ClampMax = "1")) + float SteepSlopeSlideFactor = 0.0f; + + // ======================================================================== + // API + // ======================================================================== + + /** Converts degree angles to cosine thresholds for runtime use */ + FSurfaceThresholds GetThresholds() const; + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif +}; \ No newline at end of file diff --git a/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.ts b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.ts new file mode 100644 index 0000000..f43d7ba --- /dev/null +++ b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.ts @@ -0,0 +1,27 @@ +// Source/TengriPlatformer/Movement/Core/TengriMovementConfig.ts + +import type { Float } from '/Content/UE/Float.ts'; +import type { Integer } from '/Content/UE/Integer.ts'; +import { PrimaryDataAsset } from '/Content/UE/PrimaryDataAsset.ts'; + +export class TengriMovementConfig extends PrimaryDataAsset { + public readonly MaxSpeed: Float = 800.0; + public readonly Acceleration: Float = 2048.0; + public readonly Friction: Float = 8.0; + public readonly Gravity: Float = 980.0; + public readonly RotationSpeed: Float = 360.0; + public readonly MinSpeedForRotation: Float = 10.0; + public readonly SteepSlopeSlideFactor: Float = 0.0; + + public readonly CapsuleRadius: Float = 34.0; + public readonly CapsuleHalfHeight: Float = 88.0; + public readonly MaxSlideIterations: Integer = 3; + public readonly MaxStepHeight: Float = 45.0; + + public readonly GroundSnapDistance: Float = 20.0; + public readonly GroundSnapOffset: Float = 0.15; + + public readonly WalkableAngleDeg: Float = 50.0; + public readonly SteepSlopeAngleDeg: Float = 85.0; + public readonly WallAngleDeg: Float = 95.0; +} diff --git a/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp b/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp new file mode 100644 index 0000000..50b1d4f --- /dev/null +++ b/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp @@ -0,0 +1,269 @@ +// Source/TengriPlatformer/Movement/TengriMovementComponent.cpp + +#include "TengriMovementComponent.h" +#include "Components/CapsuleComponent.h" +#include "TengriPlatformer/Movement/Collision/TengriCollisionResolver.h" + +DEFINE_LOG_CATEGORY_STATIC(LogTengriMovement, Log, All); + +// ============================================================================ +// CONSTRUCTOR +// ============================================================================ + +UTengriMovementComponent::UTengriMovementComponent() +{ + PrimaryComponentTick.bCanEverTick = true; + Velocity = FVector::ZeroVector; + bIsGrounded = false; +} + +// ============================================================================ +// INITIALIZATION +// ============================================================================ + +void UTengriMovementComponent::BeginPlay() +{ + Super::BeginPlay(); + InitializeSystem(); +} + +void UTengriMovementComponent::InitializeSystem() +{ + if (const AActor* Owner = GetOwner()) + { + OwnerCapsule = Cast(Owner->GetRootComponent()); + if (!OwnerCapsule) + { + UE_LOG(LogTengriMovement, Error, + TEXT("InitializeSystem failed: Owner root component is not a CapsuleComponent")); + SetComponentTickEnabled(false); + return; + } + } + + if (MovementConfig) + { + CachedThresholds = MovementConfig->GetThresholds(); + UE_LOG(LogTengriMovement, Log, + TEXT("System initialized. WalkableZ: %.3f"), CachedThresholds.WalkableZ); + } + else + { + UE_LOG(LogTengriMovement, Warning, + TEXT("InitializeSystem: No MovementConfig assigned")); + } +} + +// ============================================================================ +// BLUEPRINT API +// ============================================================================ + +void UTengriMovementComponent::SetInputVector(FVector NewInput) +{ + InputVector = NewInput.GetClampedToMaxSize(1.0f); + InputVector.Z = 0.0f; +} + +// ============================================================================ +// TICK +// ============================================================================ + +void UTengriMovementComponent::TickComponent( + const float DeltaTime, + const ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (!MovementConfig || !OwnerCapsule) + { + return; + } + + // Phase 1: Input -> Velocity + ApplyAccelerationAndFriction(DeltaTime); + + // Phase 2: Rotation + ApplyRotation(DeltaTime); + + // Phase 3: Gravity + ApplyGravity(DeltaTime); + + // Phase 4: Collision Resolution + FVector NewLocation = ResolveMovementWithCollision(DeltaTime); + GetOwner()->SetActorLocation(NewLocation); + + // Phase 5: Ground Snapping + FHitResult SnapHit; + const bool bJustSnapped = PerformGroundSnapping(NewLocation, SnapHit); + + if (bJustSnapped) + { + GetOwner()->SetActorLocation(NewLocation); + + // Preserve momentum on slopes + if (!InputVector.IsNearlyZero()) + { + Velocity = UTengriCollisionResolver::ProjectVelocity(Velocity, SnapHit.ImpactNormal); + } + } + + // Phase 6: State Update + // Note: MoveResult info is stored during Phase 4 + UpdateGroundedState(false, 0.f, bJustSnapped); +} + +// ============================================================================ +// PHASE 1: ACCELERATION +// ============================================================================ + +void UTengriMovementComponent::ApplyAccelerationAndFriction(const float DeltaTime) +{ + const float CurrentZ = Velocity.Z; + FVector HorizontalVelocity(Velocity.X, Velocity.Y, 0.f); + + if (!InputVector.IsNearlyZero()) + { + const FVector TargetVelocity = InputVector * MovementConfig->MaxSpeed; + HorizontalVelocity = FMath::VInterpTo( + HorizontalVelocity, + TargetVelocity, + DeltaTime, + MovementConfig->Acceleration + ); + } + else + { + HorizontalVelocity = FMath::VInterpTo( + HorizontalVelocity, + FVector::ZeroVector, + DeltaTime, + MovementConfig->Friction + ); + } + + Velocity = HorizontalVelocity; + Velocity.Z = CurrentZ; +} + +// ============================================================================ +// PHASE 2: ROTATION +// ============================================================================ + +void UTengriMovementComponent::ApplyRotation(const float DeltaTime) const +{ + if (const float MinSpeedSq = FMath::Square(MovementConfig->MinSpeedForRotation); Velocity.SizeSquared2D() > MinSpeedSq) + { + const FRotator CurrentRot = GetOwner()->GetActorRotation(); + + FRotator TargetRot = Velocity.ToOrientationRotator(); + TargetRot.Pitch = 0.0f; + TargetRot.Roll = 0.0f; + + const FRotator NewRot = FMath::RInterpConstantTo( + CurrentRot, + TargetRot, + DeltaTime, + MovementConfig->RotationSpeed + ); + + GetOwner()->SetActorRotation(NewRot); + } +} + +// ============================================================================ +// PHASE 3: GRAVITY +// ============================================================================ + +void UTengriMovementComponent::ApplyGravity(float DeltaTime) +{ + if (!bIsGrounded) + { + Velocity.Z -= MovementConfig->Gravity * DeltaTime; + } + else + { + Velocity.Z = 0.0f; + } +} + +// ============================================================================ +// PHASE 4: COLLISION RESOLUTION +// ============================================================================ + +FVector UTengriMovementComponent::ResolveMovementWithCollision(float DeltaTime) +{ + const FVector DesiredDelta = Velocity * DeltaTime; + + const FTengriSweepResult MoveResult = UTengriCollisionResolver::ResolveMovement( + this, + GetOwner()->GetActorLocation(), + DesiredDelta, + OwnerCapsule, + CachedThresholds, + MovementConfig->MaxStepHeight, + MovementConfig->MaxSlideIterations, + false + ); + + // Store for state update + if (MoveResult.bBlocked) + { + UpdateGroundedState(MoveResult.bBlocked, MoveResult.Hit.ImpactNormal.Z, false); + } + + return MoveResult.Location; +} + +// ============================================================================ +// PHASE 5: GROUND SNAPPING +// ============================================================================ + +bool UTengriMovementComponent::PerformGroundSnapping( + FVector& InOutLocation, + FHitResult& OutSnapHit) const +{ + // Only snap if we were grounded or just landed + if (!bIsGrounded) + { + return false; + } + + FVector SnapLocation; + const bool bSnapped = UTengriCollisionResolver::SnapToGround( + this, + OwnerCapsule, + MovementConfig->GroundSnapDistance, + CachedThresholds, + SnapLocation, + OutSnapHit + ); + + if (bSnapped) + { + // Add micro-offset to prevent floor penetration + InOutLocation = SnapLocation + FVector(0.f, 0.f, MovementConfig->GroundSnapOffset); + return true; + } + + return false; +} + +// ============================================================================ +// PHASE 6: STATE UPDATE +// ============================================================================ + +void UTengriMovementComponent::UpdateGroundedState( + const bool bMoveBlocked, + const float HitNormalZ, + const bool bJustSnapped) +{ + const bool bHitWalkable = bMoveBlocked && HitNormalZ >= CachedThresholds.WalkableZ; + bIsGrounded = bJustSnapped || bHitWalkable; + + // Prevent Z velocity accumulation when grounded + if (bIsGrounded && Velocity.Z < 0.f) + { + Velocity.Z = 0.f; + } +} \ No newline at end of file diff --git a/Source/TengriPlatformer/Movement/TengriMovementComponent.h b/Source/TengriPlatformer/Movement/TengriMovementComponent.h new file mode 100644 index 0000000..cd1f72d --- /dev/null +++ b/Source/TengriPlatformer/Movement/TengriMovementComponent.h @@ -0,0 +1,104 @@ +// Source/TengriPlatformer/Movement/TengriMovementComponent.h + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "TengriPlatformer/Movement/Core/TengriMovementConfig.h" +#include "TengriMovementComponent.generated.h" + +class UCapsuleComponent; + +/** + * Custom movement component for deterministic 3D platformer physics. + * Handles acceleration, friction, gravity, collision, and ground snapping. + */ +UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) +class TENGRIPLATFORMER_API UTengriMovementComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UTengriMovementComponent(); + +protected: + virtual void BeginPlay() override; + +public: + virtual void TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) override; + + // ======================================================================== + // BLUEPRINT API + // ======================================================================== + + /** + * Sets the movement input vector. Call every frame from PlayerController. + * @param NewInput - Raw input (will be clamped to 1.0 and Z zeroed) + */ + UFUNCTION(BlueprintCallable, Category = "Tengri Movement") + void SetInputVector(FVector NewInput); + + // ======================================================================== + // CONFIGURATION + // ======================================================================== + + /** Movement parameters data asset */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tengri Movement") + TObjectPtr MovementConfig; + + // ======================================================================== + // RUNTIME STATE + // ======================================================================== + + /** Current velocity in cm/s */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tengri Movement|State") + FVector Velocity; + + /** True when character is on walkable ground */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tengri Movement|State") + bool bIsGrounded = false; + + /** Cached surface thresholds (cosines) for performance */ + UPROPERTY(VisibleAnywhere, Category = "Tengri Movement|Debug") + FSurfaceThresholds CachedThresholds; + +private: + // ======================================================================== + // INTERNAL STATE + // ======================================================================== + + UPROPERTY() + TObjectPtr OwnerCapsule; + + /** Normalized input vector (Z always 0) */ + FVector InputVector = FVector::ZeroVector; + + // ======================================================================== + // INITIALIZATION + // ======================================================================== + + void InitializeSystem(); + + // ======================================================================== + // MOVEMENT PHASES + // ======================================================================== + + /** Phase 1: Apply acceleration toward input direction or friction to stop */ + void ApplyAccelerationAndFriction(float DeltaTime); + + /** Phase 2: Rotate character to face movement direction */ + void ApplyRotation(float DeltaTime) const; + + /** Phase 3: Apply gravity when airborne */ + void ApplyGravity(float DeltaTime); + + /** Phase 4: Resolve movement with collision */ + FVector ResolveMovementWithCollision(float DeltaTime); + + /** Phase 5: Snap to ground to prevent slope jitter */ + bool PerformGroundSnapping(FVector& InOutLocation, FHitResult& OutSnapHit) const; + + /** Phase 6: Update grounded state based on collision results */ + void UpdateGroundedState(bool bMoveBlocked, float HitNormalZ, bool bJustSnapped); +}; \ No newline at end of file diff --git a/Source/TengriPlatformer/Movement/TengriMovementComponent.ts b/Source/TengriPlatformer/Movement/TengriMovementComponent.ts new file mode 100644 index 0000000..d616cc8 --- /dev/null +++ b/Source/TengriPlatformer/Movement/TengriMovementComponent.ts @@ -0,0 +1,17 @@ +// Source/TengriPlatformer/Movement/TengriMovementComponent.ts + +import { ActorComponent } from '/Content/UE/ActorComponent.ts'; +import type { Vector } from '/Content/UE/Vector.ts'; +import type { DA_TengriMovementConfig } from '/Content/Movement/DA_TengriMovementConfig.ts'; + +export class TengriMovementComponent extends ActorComponent { + constructor(MovementConfig: typeof DA_TengriMovementConfig) { + super(); + + console.log(MovementConfig); + } + + public SetInputVector(NewInput: Vector): void { + console.log(NewInput); + } +} diff --git a/Source/TengriPlatformer/TengriPlatformer.Build.cs b/Source/TengriPlatformer/TengriPlatformer.Build.cs new file mode 100644 index 0000000..c5917b9 --- /dev/null +++ b/Source/TengriPlatformer/TengriPlatformer.Build.cs @@ -0,0 +1,23 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; + +public class TengriPlatformer : ModuleRules +{ + public TengriPlatformer(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); + + PrivateDependencyModuleNames.AddRange(new string[] { }); + + // Uncomment if you are using Slate UI + // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); + + // Uncomment if you are using online features + // PrivateDependencyModuleNames.Add("OnlineSubsystem"); + + // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } +} diff --git a/Source/TengriPlatformer/TengriPlatformer.cpp b/Source/TengriPlatformer/TengriPlatformer.cpp new file mode 100644 index 0000000..fb7be69 --- /dev/null +++ b/Source/TengriPlatformer/TengriPlatformer.cpp @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "TengriPlatformer.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, TengriPlatformer, "TengriPlatformer" ); diff --git a/Source/TengriPlatformer/TengriPlatformer.h b/Source/TengriPlatformer/TengriPlatformer.h new file mode 100644 index 0000000..90aad9e --- /dev/null +++ b/Source/TengriPlatformer/TengriPlatformer.h @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + diff --git a/Source/TengriPlatformerEditor.Target.cs b/Source/TengriPlatformerEditor.Target.cs new file mode 100644 index 0000000..5308772 --- /dev/null +++ b/Source/TengriPlatformerEditor.Target.cs @@ -0,0 +1,15 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class TengriPlatformerEditorTarget : TargetRules +{ + public TengriPlatformerEditorTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + DefaultBuildSettings = BuildSettingsVersion.V5; + + ExtraModuleNames.AddRange( new string[] { "TengriPlatformer" } ); + } +} diff --git a/TengriPlatformer.uproject b/TengriPlatformer.uproject index d59fa9a..e7bb291 100644 --- a/TengriPlatformer.uproject +++ b/TengriPlatformer.uproject @@ -3,6 +3,16 @@ "EngineAssociation": "5.6", "Category": "", "Description": "", + "Modules": [ + { + "Name": "TengriPlatformer", + "Type": "Runtime", + "LoadingPhase": "Default", + "AdditionalDependencies": [ + "Engine" + ] + } + ], "Plugins": [ { "Name": "ModelingToolsEditorMode", diff --git a/package.json b/package.json index 7ee2d21..c06596e 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,6 @@ "author": "", "license": "ISC", "description": "", - "imports": { - "#root/*": "./Content/*" - }, "dependencies": { "@typescript-eslint/eslint-plugin": "^8.41.0", "@typescript-eslint/parser": "^8.41.0", diff --git a/tsconfig.json b/tsconfig.json index 8eefac9..aa6643e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ // Path Mapping "baseUrl": ".", "paths": { - "#root/*": ["./Content/*"] + "/*": ["./*"] }, // Strict Type Checking