diff --git a/Content/Blueprints/BP_MainCharacter.ts b/Content/Blueprints/BP_MainCharacter.ts index eb5e3fc..61d8b96 100644 --- a/Content/Blueprints/BP_MainCharacter.ts +++ b/Content/Blueprints/BP_MainCharacter.ts @@ -92,6 +92,25 @@ export class BP_MainCharacter extends Pawn { this.CameraComponent.ProcessLookInput(new Vector(0, 0, 0), this.DeltaTime); } + /** + * Process movement input for ground-based movement + * @param actionValueX - Horizontal movement input value (-1 to 1) + * @param actionValueY - Vertical movement input value (-1 to 1) + */ + EnhancedInputActionMoveTriggered( + actionValueX: Float, + actionValueY: Float + ): void { + this.CurrentMovementInput = new Vector(actionValueY, actionValueX, 0); + } + + /** + * Reset movement input when move action is completed + */ + EnhancedInputActionMoveCompleted(): void { + this.CurrentMovementInput = new Vector(0, 0, 0); + } + /** * Initialize all systems when character spawns * Order: Toast → Debug → Movement (movement last as it may generate debug output) @@ -143,6 +162,39 @@ export class BP_MainCharacter extends Pawn { this.CameraComponent.GetCameraRotation().Yaw ) ); + + this.MovementComponent.ProcessMovementInput( + this.CurrentMovementInput, + DeltaTime + ); + this.ApplyMovementToActor(); + } + + // ════════════════════════════════════════════════════════════════════════════════════════ + // FUNCTIONS + // ════════════════════════════════════════════════════════════════════════════════════════ + + /** + * Apply calculated movement velocity to actor position + * @category Movement Application + */ + private ApplyMovementToActor(): void { + const CalculateNewLocation = ( + currentLocation: Vector, + velocity: Vector + ): Vector => + new Vector( + currentLocation.X + velocity.X * this.DeltaTime, + currentLocation.Y + velocity.Y * this.DeltaTime, + currentLocation.Z + velocity.Z * this.DeltaTime + ); + + this.SetActorLocation( + CalculateNewLocation( + this.GetActorLocation(), + this.MovementComponent.CurrentVelocity + ) + ); } // ════════════════════════════════════════════════════════════════════════════════════════ @@ -190,4 +242,9 @@ 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 fd27115..c017d89 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:987a6ba60100f8e807c12d836417b99f446064a9e7ceda68316d039e7500b76e -size 301360 +oid sha256:daac43daf46dcf7eabbf3658ecd54948c15a1fe7f8feda5e99e1f75e26a0db45 +size 346311 diff --git a/Content/Camera/Tests/FT_CameraSmoothing.uasset b/Content/Camera/Tests/FT_CameraSmoothing.uasset index 2f85901..276fded 100644 --- a/Content/Camera/Tests/FT_CameraSmoothing.uasset +++ b/Content/Camera/Tests/FT_CameraSmoothing.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e43e47a49f9b1c946953bb5a56917a7aa2d05da9207d1caa74dc382f84068db8 -size 127379 +oid sha256:e3e5102d6fdce5a4c861fcfbff23f1288f6b97231af2d3eab6c8bdb07fd646bb +size 126452 diff --git a/Content/Debug/Components/AC_DebugHUD.ts b/Content/Debug/Components/AC_DebugHUD.ts index 574be20..f12435a 100644 --- a/Content/Debug/Components/AC_DebugHUD.ts +++ b/Content/Debug/Components/AC_DebugHUD.ts @@ -16,6 +16,7 @@ import { DataTableFunctionLibrary } from '#root/UE/DataTableFunctionLibrary.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 { StringLibrary } from '#root/UE/StringLibrary.ts'; import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; import type { Text } from '#root/UE/Text.ts'; import { UEArray } from '#root/UE/UEArray.ts'; @@ -276,10 +277,19 @@ export class AC_DebugHUD extends ActorComponent { PageID: Page.PageID, Title: Page.Title, Content: + // Constants `Max Speed: ${this.MovementComponent.MovementConstants.MaxSpeed}\n` + `Acceleration: ${this.MovementComponent.MovementConstants.Acceleration}\n` + `Friction: ${this.MovementComponent.MovementConstants.Friction}\n` + - `Gravity: ${this.MovementComponent.MovementConstants.Gravity}`, + `Gravity: ${this.MovementComponent.MovementConstants.Gravity}\n` + + `\n` + // Разделитель + // Current State + `Current Velocity: ${StringLibrary.ConvVectorToString(this.MovementComponent.CurrentVelocity)}\n` + + `Speed: ${this.MovementComponent.CurrentSpeed}\n` + + `Is Grounded: ${this.MovementComponent.IsGrounded}\n` + + `Surface Type: ${this.MovementComponent.CurrentSurface}\n` + + `Movement State: ${this.MovementComponent.MovementState}\n` + + `Input Magnitude: ${this.MovementComponent.InputMagnitude}`, IsVisible: Page.IsVisible, UpdateFunction: Page.UpdateFunction, }; diff --git a/Content/Debug/Components/AC_DebugHUD.uasset b/Content/Debug/Components/AC_DebugHUD.uasset index 6b6c322..62cd1c2 100644 --- a/Content/Debug/Components/AC_DebugHUD.uasset +++ b/Content/Debug/Components/AC_DebugHUD.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8283860796501ba23679100338bb46dea61eefc14bd3ffd37ec905704e894052 -size 997076 +oid sha256:4267143968587e5701d84f79400c8e00ba0a243a2465eb2bdd575d7e12416519 +size 1025955 diff --git a/Content/Debug/Tests/FT_DebugNavigation.uasset b/Content/Debug/Tests/FT_DebugNavigation.uasset index d948b4f..516b416 100644 --- a/Content/Debug/Tests/FT_DebugNavigation.uasset +++ b/Content/Debug/Tests/FT_DebugNavigation.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1ff1b835301c55df0ffaacaa9a6d753356d908f098781fddbd3c9acc8d07c75 -size 115128 +oid sha256:a95b4f35569732268f6abc2ca69ef8b6809a76b77be457fdcd08002780cb21da +size 114730 diff --git a/Content/Debug/Tests/FT_DebugPageContentGenerator.uasset b/Content/Debug/Tests/FT_DebugPageContentGenerator.uasset index ebe6929..056f4d5 100644 --- a/Content/Debug/Tests/FT_DebugPageContentGenerator.uasset +++ b/Content/Debug/Tests/FT_DebugPageContentGenerator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79676f2e7db200d5e7dae7c4aa3e66935a40df1bb01bdf55be6604b53149b42d -size 151321 +oid sha256:a733e2a8ebf7daca900269a694780d969b2895cd59714064d88a9ac95759ca99 +size 151574 diff --git a/Content/Debug/Tests/FT_DebugSystem.uasset b/Content/Debug/Tests/FT_DebugSystem.uasset index 67681e9..20f06f6 100644 --- a/Content/Debug/Tests/FT_DebugSystem.uasset +++ b/Content/Debug/Tests/FT_DebugSystem.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:349323586c4e3320d411abca92231a6776dd59acd3c86ab93b03676d5b3a047c -size 105841 +oid sha256:d2624f24b482fa40c914637ba6ba9365a234ff3da584e7b2066e3db4fb05d9b2 +size 105674 diff --git a/Content/Levels/TestLevel.ts b/Content/Levels/TestLevel.ts index 8da62f9..db10c76 100644 --- a/Content/Levels/TestLevel.ts +++ b/Content/Levels/TestLevel.ts @@ -10,6 +10,8 @@ import { FT_DebugNavigation } from '#root/Debug/Tests/FT_DebugNavigation.ts'; import { FT_DebugPageContentGenerator } from '#root/Debug/Tests/FT_DebugPageContentGenerator.ts'; import { FT_DebugSystem } from '#root/Debug/Tests/FT_DebugSystem.ts'; import { FT_InputDeviceDetection } from '#root/Input/Tests/FT_InputDeviceDetection.ts'; +import { FT_BasicMovement } from '#root/Movement/Tests/FT_BasicMovement.ts'; +import { FT_DiagonalMovement } from '#root/Movement/Tests/FT_DiagonalMovement.ts'; import { FT_SurfaceClassification } from '#root/Movement/Tests/FT_SurfaceClassification.ts'; import { FT_ToastLimit } from '#root/Toasts/Tests/FT_ToastLimit.ts'; import { FT_ToastsDurationHandling } from '#root/Toasts/Tests/FT_ToastsDurationHandling.ts'; @@ -47,9 +49,13 @@ const InputDeviceDetectionTest = new FT_InputDeviceDetection(); InputDeviceDetectionTest.EventStartTest(); // Movement Tests +const BasicMovementTest = new FT_BasicMovement(); const SurfaceClassificationTest = new FT_SurfaceClassification(); +const DiagonalMovement = new FT_DiagonalMovement(); +BasicMovementTest.EventStartTest(); SurfaceClassificationTest.EventStartTest(); +DiagonalMovement.EventStartTest(); // Toasts Tests const ToastLimitsTest = new FT_ToastLimit(); diff --git a/Content/Levels/TestLevel.umap b/Content/Levels/TestLevel.umap index 3c483f4..0ab07b4 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:c957008c5cb0492908ad6b7dce925bd2d1c3a329e24a5d6cd03db6f5fce5d3a4 -size 103582 +oid sha256:02f10e71ac92b36e4cbba0b7663cab29284009889ae53130eacc1a98204a93e0 +size 108689 diff --git a/Content/Movement/Components/AC_Movement.ts b/Content/Movement/Components/AC_Movement.ts index b7b94fe..8a1519c 100644 --- a/Content/Movement/Components/AC_Movement.ts +++ b/Content/Movement/Components/AC_Movement.ts @@ -1,13 +1,14 @@ // Movement/Components/AC_Movement.ts import { BFL_Vectors } from '#root/Math/Libraries/BFL_Vectors.ts'; +import { E_MovementState } from '#root/Movement/Enums/E_MovementState.ts'; import { E_SurfaceType } from '#root/Movement/Enums/E_SurfaceType.ts'; import { S_AngleThresholds } from '#root/Movement/Structs/S_AngleThresholds.ts'; import type { S_MovementConstants } from '#root/Movement/Structs/S_MovementConstants.ts'; import { ActorComponent } from '#root/UE/ActorComponent.ts'; import type { Float } from '#root/UE/Float.ts'; import { MathLibrary } from '#root/UE/MathLibrary.ts'; -import type { Vector } from '#root/UE/Vector.ts'; +import { Vector } from '#root/UE/Vector.ts'; /** * Movement System Component @@ -120,6 +121,137 @@ export class AC_Movement extends ActorComponent { return SurfaceType === E_SurfaceType.None; } + /** + * 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 Movement Processing + */ + public ProcessMovementInput(InputVector: Vector, DeltaTime: Float): void { + if (this.IsInitialized) { + this.InputMagnitude = MathLibrary.VectorLength(InputVector); + + // Only process movement on walkable surfaces + if (this.IsSurfaceWalkable(this.CurrentSurface) && this.IsGrounded) { + this.ProcessGroundMovement(InputVector, DeltaTime); + } else { + this.ApplyFriction(DeltaTime); + } + + // Always apply gravity + this.ApplyGravity(DeltaTime); + + // Update movement state + this.UpdateMovementState(); + + // Calculate current speed for debug + this.UpdateCurrentSpeed(); + } + } + + /** + * Process ground-based movement with acceleration and max speed limits + * @param InputVector - Normalized input direction + * @param DeltaTime - Frame delta time + * @category Movement Processing + */ + private ProcessGroundMovement(InputVector: Vector, DeltaTime: Float): void { + if (this.InputMagnitude > 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 + ); + + this.CurrentVelocity = MathLibrary.VInterpTo( + new Vector(this.CurrentVelocity.X, this.CurrentVelocity.Y, 0), + CalculateTargetVelocity(InputVector, this.MovementConstants.MaxSpeed), + DeltaTime, + this.MovementConstants.Acceleration + ); + } else { + // Apply friction when no input + this.ApplyFriction(DeltaTime); + } + } + + /** + * Apply friction to horizontal velocity + * @param DeltaTime - Frame delta time + * @category Movement Processing + */ + private ApplyFriction(DeltaTime: Float): void { + this.CurrentVelocity = MathLibrary.VInterpTo( + new Vector(this.CurrentVelocity.X, this.CurrentVelocity.Y, 0), + new Vector(0, 0, this.CurrentVelocity.Z), + DeltaTime, + this.MovementConstants.Friction + ); + } + + /** + * Apply gravity to vertical velocity + * @param DeltaTime - Frame delta time + * @category Movement Processing + */ + private ApplyGravity(DeltaTime: Float): void { + if (!this.IsGrounded) { + const ApplyGravityForce = ( + velocityZ: Float, + gravity: Float, + deltaTime: Float + ): Float => velocityZ - gravity * deltaTime; + + // Apply gravity when airborne + this.CurrentVelocity = new Vector( + this.CurrentVelocity.X, + this.CurrentVelocity.Y, + ApplyGravityForce( + this.CurrentVelocity.Z, + this.MovementConstants.Gravity, + DeltaTime + ) + ); + } else { + // Zero out vertical velocity when grounded + this.CurrentVelocity = new Vector( + this.CurrentVelocity.X, + this.CurrentVelocity.Y, + 0 + ); + } + } + + /** + * Update movement state based on current conditions + * @category Movement Processing + */ + private UpdateMovementState(): void { + if (!this.IsGrounded) { + this.MovementState = E_MovementState.Airborne; + } else if (this.InputMagnitude > 0.01) { + this.MovementState = E_MovementState.Walking; + } else { + this.MovementState = E_MovementState.Idle; + } + } + + /** + * Update current speed for debug display + * @category Movement Processing + */ + private UpdateCurrentSpeed(): void { + // Calculate horizontal speed only (ignore vertical component) + this.CurrentSpeed = MathLibrary.VectorLength( + new Vector(this.CurrentVelocity.X, this.CurrentVelocity.Y, 0) + ); + } + /** * Initialize movement system with angle conversion * Converts degree thresholds to radians for runtime performance @@ -151,7 +283,7 @@ export class AC_Movement extends ActorComponent { */ public readonly MovementConstants: S_MovementConstants = { MaxSpeed: 600.0, - Acceleration: 2000.0, + Acceleration: 10.0, Friction: 8.0, Gravity: 980.0, }; @@ -185,4 +317,46 @@ export class AC_Movement extends ActorComponent { * @category Debug */ public IsInitialized = false; + + /** + * Current character velocity in world space + * Updated every frame by movement calculations + * @category Movement State + */ + public CurrentVelocity: Vector = new Vector(0, 0, 0); + + /** + * Ground contact state - true when character is on walkable surface + * Used for gravity application and movement restrictions + * @category Movement State + */ + public IsGrounded: boolean = true; + + /** + * Type of surface currently under character + * Determines available movement options + * @category Movement State + */ + public CurrentSurface: E_SurfaceType = E_SurfaceType.Walkable; + + /** + * Current movement state of character + * Used for animation and game logic decisions + * @category Movement State + */ + public MovementState: E_MovementState = E_MovementState.Idle; + + /** + * Magnitude of current movement input (0-1) + * Used for determining if character should be moving + * @category Movement State + */ + public InputMagnitude: Float = 0.0; + + /** + * Current movement speed (magnitude of horizontal velocity) + * Calculated from CurrentVelocity for debug display + * @category Movement State + */ + public CurrentSpeed: Float = 0.0; } diff --git a/Content/Movement/Components/AC_Movement.uasset b/Content/Movement/Components/AC_Movement.uasset index 9e9e690..b4a2b44 100644 --- a/Content/Movement/Components/AC_Movement.uasset +++ b/Content/Movement/Components/AC_Movement.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5de764654a02fe07260aba6bdfd9ef87892e2c1760460d6662b1ede23b12bfd -size 123795 +oid sha256:39c43ef35c676cc50695885cbbef5fcc90388d39b8bb15afa8e52df2890d7916 +size 338671 diff --git a/Content/Movement/Enums/E_MovementState.ts b/Content/Movement/Enums/E_MovementState.ts new file mode 100644 index 0000000..94fc63e --- /dev/null +++ b/Content/Movement/Enums/E_MovementState.ts @@ -0,0 +1,7 @@ +// Movement/Enums/E_MovementState.ts + +export enum E_MovementState { + Idle = 'Idle', + Walking = 'Walking', + Airborne = 'Airborne', +} diff --git a/Content/Movement/Enums/E_MovementState.uasset b/Content/Movement/Enums/E_MovementState.uasset new file mode 100644 index 0000000..0bdade2 --- /dev/null +++ b/Content/Movement/Enums/E_MovementState.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5639ddb4e55c203b60b4bdc8896a5afa032e69c189f82c48692de7515bb81ec1 +size 2572 diff --git a/Content/Movement/ManualTestingChecklist.md b/Content/Movement/ManualTestingChecklist.md index 0aa4897..c3a3077 100644 --- a/Content/Movement/ManualTestingChecklist.md +++ b/Content/Movement/ManualTestingChecklist.md @@ -21,7 +21,7 @@ ### 2.1 Default значения - [ ] **MaxSpeed = 600.0** - значение установлено по умолчанию -- [ ] **Acceleration = 2000.0** - значение установлено по умолчанию +- [ ] **Acceleration = 10.0** - значение установлено по умолчанию (уменьшено для плавности) - [ ] **Friction = 8.0** - значение установлено по умолчанию - [ ] **Gravity = 980.0** - значение установлено по умолчанию @@ -32,9 +32,94 @@ --- -## Критерии прохождения -- [ ] Все пункты выполнены -- [ ] Система инициализируется без ошибок -- [ ] Default константы соответствуют спецификации +## 3. Базовое движение (Этап 7) -**Примечание:** Большая часть Movement System тестируется автотестами (FT_SurfaceClassification с 10 test cases). Ручное тестирование фокусируется на базовой инициализации. +### 3.1 Управление клавиатурой +- [ ] **W** - персонаж движется вперед (+X направление) +- [ ] **S** - персонаж движется назад (-X направление) +- [ ] **A** - персонаж движется влево (+Y направление) +- [ ] **D** - персонаж движется вправо (-Y направление) +- [ ] **Отсутствие input** - персонаж останавливается + +### 3.2 Управление геймпадом +- [ ] **Left Stick Up** - движение вперед +- [ ] **Left Stick Down** - движение назад +- [ ] **Left Stick Left** - движение влево +- [ ] **Left Stick Right** - движение вправо +- [ ] **Stick в центре** - персонаж останавливается + +### 3.3 Физика движения +- [ ] **Плавное ускорение** - персонаж набирает скорость постепенно при нажатии клавиш +- [ ] **Плавное торможение** - персонаж останавливается плавно при отпускании клавиш +- [ ] **MaxSpeed limit** - скорость не превышает 600.0 units/sec +- [ ] **Диагональное движение** - скорость диагонального движения равна прямому (не быстрее) +- [ ] **Стабильное поведение** - нет рывков, заиканий или неожиданных ускорений + +### 3.4 Состояния движения +- [ ] **Idle state** - MovementState = Idle когда персонаж стоит +- [ ] **Walking state** - MovementState = Walking при движении +- [ ] **InputMagnitude** - корректно отражает силу input (0-1) +- [ ] **CurrentSpeed** - показывает текущую горизонтальную скорость + +--- + +## 4. Debug HUD Integration + +### 4.1 Movement Constants Page (Page 1) +- [ ] **Константы** отображаются корректно: + - Max Speed: 600 + - Acceleration: 10 + - Friction: 8 + - Gravity: 980 +- [ ] **Текущее состояние** отображается: + - Current Velocity: X, Y, Z компоненты + - Speed: горизонтальная скорость + - Is Grounded: Yes (пока всегда true) + - Surface Type: Walkable (пока всегда) + - Movement State: Idle/Walking + - Input Magnitude: 0.00-1.00 + +### 4.2 Реальное время обновления +- [ ] **Velocity** изменяется в реальном времени при движении +- [ ] **Speed** корректно показывает magnitude горизонтальной скорости +- [ ] **Movement State** переключается между Idle и Walking +- [ ] **Input Magnitude** отражает силу нажатия (особенно на геймпаде) + +--- + +## 5. Автотесты Integration + +### 5.1 FT_BasicMovement +- [ ] **Тест проходит** - инициализация, ускорение, торможение, состояния +- [ ] **No console errors** при выполнении автотеста + +### 5.2 FT_DiagonalMovement +- [ ] **Тест проходит** - диагональное движение не быстрее прямого +- [ ] **Input normalization** работает корректно + +--- + +## 6. Performance + +### 6.1 Производительность +- [ ] **Stable 60+ FPS** при активном движении +- [ ] **No memory leaks** при длительном использовании +- [ ] **Smooth movement** без микро-заиканий + +### 6.2 Отзывчивость +- [ ] **Instant response** на нажатие клавиш (нет input lag) +- [ ] **Smooth transitions** между состояниями движения +- [ ] **Consistent timing** независимо от FPS + +--- + +## Критерии прохождения +- [ ] Все основные направления движения работают +- [ ] Физика движения плавная и отзывчивая +- [ ] MaxSpeed limit соблюдается +- [ ] Диагональное движение не дает преимущества в скорости +- [ ] Debug HUD показывает корректные данные движения +- [ ] Автотесты проходят успешно +- [ ] Performance стабильная + +**Примечание:** Этап 7 фокусируется на базовом движении по плоскости. Camera-relative движение и поворот персонажа будут в этапе 8. diff --git a/Content/Movement/TDD.md b/Content/Movement/TDD.md index 5593350..57739d4 100644 --- a/Content/Movement/TDD.md +++ b/Content/Movement/TDD.md @@ -3,7 +3,7 @@ # Система Movement - Техническая Документация ## Обзор -Детерминированная система движения для 3D-платформера с точной классификацией поверхностей и математически предсказуемым поведением. Система обеспечивает основу для всех механик перемещения персонажа в игровом мире. +Детерминированная система движения для 3D-платформера с точной классификацией поверхностей и полнофункциональным базовым движением. Система обеспечивает математически предсказуемое поведение как для классификации поверхностей, так и для физики движения персонажа с плавным ускорением и торможением. ## Архитектурные принципы - **Детерминизм:** Математически предсказуемые результаты для одинаковых входных данных @@ -24,6 +24,10 @@ - `InitializeMovementSystem()` - Инициализация с конвертацией углов - `ClassifySurface()` - Определение типа поверхности по normal вектору - Приватные методы проверки типов (`IsSurfaceWalkable()`, `IsSurfaceSteep()` и др.) +- `ProcessMovementInput()` - Обработка input и расчет velocity +- `ProcessGroundMovement()` - VInterpTo physics для плавного движения +- `ApplyFriction()` - Система торможения через VInterpTo +- `ApplyGravity()` - Вертикальная физика для airborne состояний ### BFL_Vectors (Blueprint Function Library) **Ответственности:** @@ -98,6 +102,22 @@ interface S_SurfaceTestCase { } ``` +## Физика движения (Этап 7) + +### VInterpTo Movement System +Основная логика движения использует VInterpTo для плавного ускорения и торможения. + +### E_MovementState (Movement States) +- Idle: Персонаж стоит на месте +- Walking: Движение по земле +- Airborne: В воздухе (падение/прыжок) + +### Input Processing Chain +Enhanced Input → BP_MainCharacter → AC_Movement → Apply to position + +### Diagonal Movement Prevention +Система нормализации input предотвращает diagonal speed boost. + ## Математическая основа ### Расчет угла поверхности @@ -165,6 +185,12 @@ TestCases: S_SurfaceTestCase[] = [ ] ``` +### FT_BasicMovement (Movement Physics) +Тестирует acceleration, friction, state transitions + +### FT_DiagonalMovement (Input Normalization) +Тестирует предотвращение diagonal speed boost + ### Test Coverage - **100% методов:** Все публичные функции покрыты тестами - **Boundary testing:** Проверка поведения на границах диапазонов @@ -217,7 +243,7 @@ ClassifySurface(SurfaceNormal: Vector, AngleThresholds: S_AngleThresholds): E_Su ```typescript readonly MovementConstants: S_MovementConstants = { MaxSpeed: 600.0, // Максимальная скорость персонажа - Acceleration: 2000.0, // Ускорение при движении + Acceleration: 10.0, // Ускорение при движении Friction: 8.0, // Коэффициент трения Gravity: 980.0 // Сила гравитации (UE units/s²) } diff --git a/Content/Movement/Tests/FT_BasicMovement.ts b/Content/Movement/Tests/FT_BasicMovement.ts new file mode 100644 index 0000000..aa23b27 --- /dev/null +++ b/Content/Movement/Tests/FT_BasicMovement.ts @@ -0,0 +1,128 @@ +// Movement/Tests/FT_BasicMovement.ts + +import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts'; +import { E_MovementState } from '#root/Movement/Enums/E_MovementState.ts'; +import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts'; +import { FunctionalTest } from '#root/UE/FunctionalTest.ts'; +import { Vector } from '#root/UE/Vector.ts'; + +/** + * Functional Test: Basic Movement System + * Tests fundamental movement mechanics: acceleration, friction, max speed + * Validates movement state transitions and input processing + */ +export class FT_BasicMovement extends FunctionalTest { + // ════════════════════════════════════════════════════════════════════════════════════════ + // GRAPHS + // ════════════════════════════════════════════════════════════════════════════════════════ + + // ──────────────────────────────────────────────────────────────────────────────────────── + // EventGraph + // ──────────────────────────────────────────────────────────────────────────────────────── + + /** + * Test execution - validates basic movement functionality + * Tests initialization, input processing, state management + */ + EventStartTest(): void { + // Initialize movement system + this.MovementComponent.InitializeMovementSystem(); + + // Test 1: Initialization + if (this.MovementComponent.IsInitialized) { + // Test 2: Initial state should be Idle + if (this.MovementComponent.MovementState === E_MovementState.Idle) { + // Test 3: Process forward input and verify acceleration + this.MovementComponent.ProcessMovementInput( + new Vector(1.0, 0, 0), // Forward input + 0.016 // 60 FPS delta + ); + + if (this.MovementComponent.CurrentVelocity.X > 0) { + // Test 4: Movement state should change to Walking + if ( + (this.MovementComponent.MovementState as string) === + (E_MovementState.Walking as string) + ) { + // Test 5: Process multiple frames to test max speed limit + for (let i = 0; i < 100; i++) { + this.MovementComponent.ProcessMovementInput( + new Vector(1.0, 0, 0), + 0.016 + ); + } + + // Verify max speed is not exceeded + if ( + this.MovementComponent.CurrentSpeed <= + this.MovementComponent.MovementConstants.MaxSpeed + 1 + ) { + // Test 6: Test friction by removing input + for (let i = 0; i < 50; i++) { + this.MovementComponent.ProcessMovementInput( + new Vector(0, 0, 0), // No input + 0.016 + ); + } + + // Verify friction slowed down the character + if (this.MovementComponent.CurrentSpeed < 50) { + // Test 7: Verify state changed back to Idle when stopped + if ( + this.MovementComponent.MovementState === E_MovementState.Idle + ) { + this.FinishTest(EFunctionalTestResult.Succeeded); + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + `Movement state should be Idle when stopped, got ${this.MovementComponent.MovementState as string}` + ); + } + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + `Friction should reduce speed, current speed: ${this.MovementComponent.CurrentSpeed}` + ); + } + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + `Speed ${this.MovementComponent.CurrentSpeed} exceeds MaxSpeed ${this.MovementComponent.MovementConstants.MaxSpeed}` + ); + } + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + `Movement state should be Walking with input, got ${this.MovementComponent.MovementState}` + ); + } + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + 'Forward input should produce forward velocity' + ); + } + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + `Initial movement state should be Idle, got ${this.MovementComponent.MovementState}` + ); + } + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + 'Movement system failed to initialize' + ); + } + } + + // ════════════════════════════════════════════════════════════════════════════════════════ + // VARIABLES + // ════════════════════════════════════════════════════════════════════════════════════════ + + /** + * Movement system component - component under test + * @category Components + */ + private MovementComponent = new AC_Movement(); +} diff --git a/Content/Movement/Tests/FT_BasicMovement.uasset b/Content/Movement/Tests/FT_BasicMovement.uasset new file mode 100644 index 0000000..7f3abed --- /dev/null +++ b/Content/Movement/Tests/FT_BasicMovement.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23561c3a3ee1b8a815efcc691b62c91e0aab438e792c47d9c2266d42a5c3526e +size 208643 diff --git a/Content/Movement/Tests/FT_DiagonalMovement.ts b/Content/Movement/Tests/FT_DiagonalMovement.ts new file mode 100644 index 0000000..1623373 --- /dev/null +++ b/Content/Movement/Tests/FT_DiagonalMovement.ts @@ -0,0 +1,97 @@ +// Movement/Tests/FT_DiagonalMovement.ts + +import { AC_Movement } from '#root/Movement/Components/AC_Movement.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: Diagonal Movement Speed Control + * Tests that diagonal movement is not faster than cardinal movement + * Validates input normalization prevents diagonal speed boost + */ +export class FT_DiagonalMovement extends FunctionalTest { + // ════════════════════════════════════════════════════════════════════════════════════════ + // GRAPHS + // ════════════════════════════════════════════════════════════════════════════════════════ + + // ──────────────────────────────────────────────────────────────────────────────────────── + // EventGraph + // ──────────────────────────────────────────────────────────────────────────────────────── + + /** + * Test execution - validates diagonal movement speed control + * Tests cardinal vs diagonal movement speeds + */ + EventStartTest(): void { + // Initialize movement system + this.MovementComponent.InitializeMovementSystem(); + + // Test 1: Cardinal movement (forward only) + for (let i = 0; i < 100; i++) { + this.MovementComponent.ProcessMovementInput( + new Vector(1.0, 0, 0), // Pure forward + 0.016 + ); + } + const cardinalSpeed = this.MovementComponent.CurrentSpeed; + + // Reset velocity + this.MovementComponent.CurrentVelocity = new Vector(0, 0, 0); + + // Test 2: Diagonal movement (forward + right) + for (let i = 0; i < 100; i++) { + this.MovementComponent.ProcessMovementInput( + new Vector(1.0, 1.0, 0), // Diagonal input + 0.016 + ); + } + const diagonalSpeed = this.MovementComponent.CurrentSpeed; + + // Test 3: Diagonal should not be faster than cardinal + if (diagonalSpeed <= cardinalSpeed + 1) { + // Small tolerance for floating point + // Test 4: Both speeds should be close to MaxSpeed + if ( + cardinalSpeed >= + this.MovementComponent.MovementConstants.MaxSpeed - 50 && + diagonalSpeed >= this.MovementComponent.MovementConstants.MaxSpeed - 50 + ) { + // Test 5: Test input normalization directly + const rawDiagonalInput = new Vector(1.0, 1.0, 0); + const inputMagnitude = MathLibrary.VectorLength(rawDiagonalInput); + + if (inputMagnitude > 1.0) { + // This confirms our diagonal input needs normalization + this.FinishTest(EFunctionalTestResult.Succeeded); + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + `Diagonal input magnitude should be > 1.0, got ${inputMagnitude}` + ); + } + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + `Speeds too low: Cardinal=${cardinalSpeed}, Diagonal=${diagonalSpeed}, Expected~${this.MovementComponent.MovementConstants.MaxSpeed}` + ); + } + } else { + this.FinishTest( + EFunctionalTestResult.Failed, + `Diagonal speed ${diagonalSpeed} exceeds cardinal speed ${cardinalSpeed}` + ); + } + } + + // ════════════════════════════════════════════════════════════════════════════════════════ + // VARIABLES + // ════════════════════════════════════════════════════════════════════════════════════════ + + /** + * Movement system component - component under test + * @category Components + */ + private MovementComponent = new AC_Movement(); +} diff --git a/Content/Movement/Tests/FT_DiagonalMovement.uasset b/Content/Movement/Tests/FT_DiagonalMovement.uasset new file mode 100644 index 0000000..d7dea1e --- /dev/null +++ b/Content/Movement/Tests/FT_DiagonalMovement.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:468e000881430761e2f0bee7896ec89a888bf1428a5e3ae1797e2250df9798d5 +size 181961 diff --git a/Content/Movement/Tests/FT_SurfaceClassification.uasset b/Content/Movement/Tests/FT_SurfaceClassification.uasset index 8cb95f4..e114444 100644 --- a/Content/Movement/Tests/FT_SurfaceClassification.uasset +++ b/Content/Movement/Tests/FT_SurfaceClassification.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c68ba8e62117f19f74ea3d85dc00411715a55ad46684492e80f959d025eae36f -size 118786 +oid sha256:d81e4f35a22dc77e7548f7f2920f1c97d25d7cd83027f0e4908a18f34e58c10d +size 117897 diff --git a/Content/UE/Actor.ts b/Content/UE/Actor.ts index a72eeb0..b7fd09f 100644 --- a/Content/UE/Actor.ts +++ b/Content/UE/Actor.ts @@ -2,9 +2,23 @@ import { Name } from '#root/UE/Name.ts'; import { UEObject } from '#root/UE/UEObject.ts'; +import { Vector } from '#root/UE/Vector.ts'; export class Actor extends UEObject { constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) { super(outer, name); } + + public SetActorLocation( + NewLocation: Vector = new Vector(), + Sweep: boolean = false, + Teleport: boolean = false + ): void { + console.log(NewLocation, Sweep, Teleport); + // Implementation for setting actor location + } + + public GetActorLocation(): Vector { + return new Vector(); // Placeholder implementation + } } diff --git a/Content/UE/MathLibrary.ts b/Content/UE/MathLibrary.ts index 8991f46..791260d 100644 --- a/Content/UE/MathLibrary.ts +++ b/Content/UE/MathLibrary.ts @@ -4,7 +4,7 @@ import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; import type { Color } from '#root/UE/Color.ts'; import type { Float } from '#root/UE/Float.ts'; import { LinearColor } from '#root/UE/LinearColor.ts'; -import type { Vector } from '#root/UE/Vector.ts'; +import { Vector } from '#root/UE/Vector.ts'; /** * System Library: Core Mathematical Functions @@ -161,6 +161,52 @@ class MathLibraryClass extends BlueprintFunctionLibrary { 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); + } } /** diff --git a/Content/UE/StringLibrary.ts b/Content/UE/StringLibrary.ts index 259b199..29848d2 100644 --- a/Content/UE/StringLibrary.ts +++ b/Content/UE/StringLibrary.ts @@ -1,6 +1,7 @@ // UE/StringLibrary.ts import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts'; +import type { Vector } from '#root/UE/Vector.ts'; class StringLibraryClass extends BlueprintFunctionLibrary { constructor( @@ -13,6 +14,10 @@ class StringLibraryClass extends BlueprintFunctionLibrary { 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();