[code] deterministic ground movement system
parent
98ce2bb903
commit
01ef4abe50
|
|
@ -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)
BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Camera/Tests/FT_CameraSmoothing.uasset (Stored with Git LFS)
BIN
Content/Camera/Tests/FT_CameraSmoothing.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
BIN
Content/Debug/Components/AC_DebugHUD.uasset (Stored with Git LFS)
BIN
Content/Debug/Components/AC_DebugHUD.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Debug/Tests/FT_DebugNavigation.uasset (Stored with Git LFS)
BIN
Content/Debug/Tests/FT_DebugNavigation.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Debug/Tests/FT_DebugPageContentGenerator.uasset (Stored with Git LFS)
BIN
Content/Debug/Tests/FT_DebugPageContentGenerator.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Debug/Tests/FT_DebugSystem.uasset (Stored with Git LFS)
BIN
Content/Debug/Tests/FT_DebugSystem.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -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)
BIN
Content/Levels/TestLevel.umap (Stored with Git LFS)
Binary file not shown.
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
BIN
Content/Movement/Components/AC_Movement.uasset (Stored with Git LFS)
BIN
Content/Movement/Components/AC_Movement.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
|||
// Movement/Enums/E_MovementState.ts
|
||||
|
||||
export enum E_MovementState {
|
||||
Idle = 'Idle',
|
||||
Walking = 'Walking',
|
||||
Airborne = 'Airborne',
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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²)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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();
|
||||
}
|
||||
Binary file not shown.
BIN
Content/Movement/Tests/FT_SurfaceClassification.uasset (Stored with Git LFS)
BIN
Content/Movement/Tests/FT_SurfaceClassification.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue