[code] deterministic ground movement system

main
Nikolay Petrov 2025-09-19 04:25:32 +05:00
parent 98ce2bb903
commit 01ef4abe50
24 changed files with 694 additions and 30 deletions

View File

@ -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);
}

BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

View File

@ -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,
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Content/Debug/Tests/FT_DebugSystem.uasset (Stored with Git LFS)

Binary file not shown.

View File

@ -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();

BIN
Content/Levels/TestLevel.umap (Stored with Git LFS)

Binary file not shown.

View File

@ -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;
}

Binary file not shown.

View File

@ -0,0 +1,7 @@
// Movement/Enums/E_MovementState.ts
export enum E_MovementState {
Idle = 'Idle',
Walking = 'Walking',
Airborne = 'Airborne',
}

BIN
Content/Movement/Enums/E_MovementState.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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.

View File

@ -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²)
}

View File

@ -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();
}

BIN
Content/Movement/Tests/FT_BasicMovement.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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();
}

BIN
Content/Movement/Tests/FT_DiagonalMovement.uasset (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -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
}
}

View File

@ -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);
}
}
/**

View File

@ -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();