1305 lines
41 KiB
Markdown
1305 lines
41 KiB
Markdown
# Movement System - Technical Documentation (Stage 9 Refactored)
|
||
|
||
## Обзор
|
||
Детерминированная система движения для 3D-платформера, построенная на принципе **Functional Core, Imperative Shell**. Система обеспечивает математически предсказуемое поведение через композицию чистых функциональных модулей, с точной классификацией поверхностей, swept collision detection и ground snapping.
|
||
|
||
**Версия:** Stage 9 (Post-Refactoring)
|
||
**Архитектурный паттерн:** Pipeline Processor + Pure Function Libraries
|
||
**Статус:** Production Ready ✅
|
||
|
||
---
|
||
|
||
## Архитектурные принципы
|
||
|
||
### Core Design Philosophy
|
||
1. **Functional Core, Imperative Shell**
|
||
- Pure business logic в BFL_* модулях
|
||
- Imperative framework integration в AC_Movement
|
||
|
||
2. **Immutable State Transformation**
|
||
- `S_MovementState → ProcessMovement() → S_MovementState`
|
||
- Никогда не мутируем входящее состояние
|
||
|
||
3. **Pipeline Processing**
|
||
- Четкие последовательные фазы обработки
|
||
- Каждая фаза читает результаты предыдущей
|
||
|
||
4. **Separation of Concerns**
|
||
- AC_Movement: координация и framework integration
|
||
- BFL_MovementProcessor: бизнес-логика движения
|
||
- BFL_*: специализированные подсистемы
|
||
|
||
5. **Data-Oriented Design**
|
||
- Явные структуры данных (S_MovementState, S_MovementInput)
|
||
- Функции оперируют данными, не прячут их
|
||
|
||
---
|
||
|
||
## Компоненты системы
|
||
|
||
### 1. AC_Movement (Component - Imperative Shell)
|
||
|
||
**Роль:** Thin orchestration layer между UE Actor system и функциональной логикой
|
||
|
||
**Ответственности:**
|
||
- Lifecycle management (инициализация, cleanup)
|
||
- Component references (CapsuleComponent, DebugHUD)
|
||
- State storage (CurrentMovementState)
|
||
- Framework integration (SetActorLocation, SetActorRotation)
|
||
- Debug visualization coordination
|
||
|
||
**Ключевые методы:**
|
||
|
||
```typescript
|
||
// Инициализация системы
|
||
InitializeMovementSystem(
|
||
CapsuleComponentRef: CapsuleComponent | null,
|
||
DebugHUDComponentRef: AC_DebugHUD | null
|
||
): void
|
||
|
||
// Обработка движения (главная точка входа)
|
||
ProcessMovementInput(
|
||
InputVector: Vector,
|
||
DeltaTime: Float
|
||
): void
|
||
|
||
// Debug
|
||
UpdateDebugPage(): void
|
||
```
|
||
|
||
**Приватные поля:**
|
||
```typescript
|
||
CurrentMovementState: S_MovementState // Текущее состояние
|
||
Config: DA_MovementConfig // Конфигурация
|
||
AngleThresholdsRads: S_AngleThresholds // Кэшированные пороги в радианах
|
||
CapsuleComponent: CapsuleComponent | null // Ссылка на капсулу
|
||
DebugHUDComponent: AC_DebugHUD | null // Ссылка на debug HUD
|
||
IsInitialized: boolean // Флаг инициализации
|
||
```
|
||
|
||
**Размер:** ~230 LOC (↓ 62% от original)
|
||
|
||
---
|
||
|
||
### 2. BFL_MovementProcessor (Core - Functional Heart)
|
||
|
||
**Роль:** Unified movement processing pipeline - центральная точка всей логики движения
|
||
|
||
**Ответственности:**
|
||
- Orchestration всех подсистем в правильном порядке
|
||
- State transformation (CurrentState + Input → NextState)
|
||
- Phase sequencing и data flow
|
||
- Integration между subsystems
|
||
|
||
**Главный метод:**
|
||
|
||
```typescript
|
||
ProcessMovement(
|
||
CurrentState: S_MovementState,
|
||
Input: S_MovementInput,
|
||
IsShowVisualDebug: boolean = false
|
||
): S_MovementState
|
||
```
|
||
|
||
**Processing Pipeline (6 фаз):**
|
||
|
||
```typescript
|
||
// PHASE 1: INPUT & ROTATION
|
||
├─ Calculate input magnitude
|
||
└─ Update character rotation (BFL_RotationController)
|
||
|
||
// PHASE 2: GROUND DETECTION
|
||
├─ Check ground with trace (BFL_GroundProbe)
|
||
├─ Determine IsGrounded
|
||
└─ Classify surface type (BFL_SurfaceClassifier)
|
||
|
||
// PHASE 3: PHYSICS CALCULATION
|
||
├─ Calculate ground velocity (BFL_Kinematics) [if grounded]
|
||
│ OR Apply air friction (BFL_Kinematics) [if airborne]
|
||
├─ Apply gravity (BFL_Kinematics)
|
||
└─ Calculate horizontal speed
|
||
|
||
// PHASE 4: MOVEMENT APPLICATION (Sweep)
|
||
├─ Convert velocity to delta
|
||
├─ Perform swept collision (BFL_CollisionResolver)
|
||
└─ Calculate slide vector if blocked
|
||
|
||
// PHASE 5: GROUND SNAPPING
|
||
└─ Snap to ground if conditions met (BFL_GroundProbe)
|
||
|
||
// PHASE 6: STATE DETERMINATION
|
||
└─ Determine movement state (BFL_MovementStateMachine)
|
||
|
||
// RETURN: Complete new S_MovementState
|
||
```
|
||
|
||
**Вспомогательные методы:**
|
||
```typescript
|
||
CreateInitialState(
|
||
Location: Vector,
|
||
Rotation: Rotator
|
||
): S_MovementState
|
||
```
|
||
|
||
**Purity:** Impure (из-за collision traces), но deterministic
|
||
**Размер:** ~260 LOC
|
||
|
||
---
|
||
|
||
### 3. BFL_Kinematics (Physics Library)
|
||
|
||
**Роль:** Pure physics calculations для движения
|
||
|
||
**Ключевые методы:**
|
||
|
||
```typescript
|
||
// Ground movement с acceleration
|
||
CalculateGroundVelocity(
|
||
CurrentVelocity: Vector,
|
||
InputVector: Vector,
|
||
DeltaTime: Float,
|
||
Config: DA_MovementConfig
|
||
): Vector
|
||
|
||
// Friction (deceleration)
|
||
CalculateFriction(
|
||
CurrentVelocity: Vector,
|
||
DeltaTime: Float,
|
||
Config: DA_MovementConfig
|
||
): Vector
|
||
|
||
// Gravity application
|
||
CalculateGravity(
|
||
CurrentVelocity: Vector,
|
||
IsGrounded: boolean,
|
||
Config: DA_MovementConfig
|
||
): Vector
|
||
|
||
// Horizontal speed query
|
||
GetHorizontalSpeed(Velocity: Vector): Float
|
||
```
|
||
|
||
**Характеристики:**
|
||
- ✅ Pure functions
|
||
- ✅ VInterpTo для smooth movement
|
||
- ✅ Frame-rate independent (uses DeltaTime)
|
||
|
||
---
|
||
|
||
### 4. BFL_CollisionResolver (Collision Library)
|
||
|
||
**Роль:** Swept collision detection и surface sliding
|
||
|
||
**Ключевые методы:**
|
||
|
||
```typescript
|
||
// Главный swept trace
|
||
PerformSweep(
|
||
StartLocation: Vector,
|
||
DesiredDelta: Vector,
|
||
CapsuleComponent: CapsuleComponent | null,
|
||
Config: DA_MovementConfig,
|
||
DeltaTime: Float,
|
||
IsShowVisualDebug: boolean
|
||
): S_SweepResult
|
||
|
||
// Surface sliding projection
|
||
ProjectOntoSurface(
|
||
MovementDelta: Vector,
|
||
SurfaceNormal: Vector
|
||
): Vector
|
||
|
||
// Slide vector calculation
|
||
CalculateSlideVector(
|
||
SweepResult: S_SweepResult,
|
||
OriginalDelta: Vector,
|
||
StartLocation: Vector
|
||
): Vector
|
||
|
||
// Adaptive step size для swept trace
|
||
CalculateStepSize(
|
||
Velocity: Vector,
|
||
DeltaTime: Float,
|
||
Config: DA_MovementConfig
|
||
): Float
|
||
```
|
||
|
||
**Характеристики:**
|
||
- ⚠️ Impure (world traces)
|
||
- ✅ Deterministic stepping
|
||
- ✅ Tunneling protection
|
||
- ✅ Adaptive precision
|
||
|
||
---
|
||
|
||
### 5. BFL_GroundProbe (Ground Detection Library)
|
||
|
||
**Роль:** Ground detection, snapping, surface queries
|
||
|
||
**Ключевые методы:**
|
||
|
||
```typescript
|
||
// Ground detection trace
|
||
CheckGround(
|
||
CharacterLocation: Vector,
|
||
CapsuleComponent: CapsuleComponent | null,
|
||
AngleThresholdsRads: S_AngleThresholds,
|
||
Config: DA_MovementConfig,
|
||
IsShowVisualDebug: boolean
|
||
): HitResult
|
||
|
||
// Ground snapping calculation
|
||
CalculateSnapLocation(
|
||
CurrentLocation: Vector,
|
||
GroundHit: HitResult,
|
||
CapsuleComponent: CapsuleComponent | null,
|
||
SnapThreshold: Float
|
||
): Vector
|
||
|
||
// Snapping condition check
|
||
ShouldSnapToGround(
|
||
CurrentVelocityZ: Float,
|
||
GroundHit: HitResult,
|
||
IsGrounded: boolean
|
||
): boolean
|
||
|
||
// Surface type query
|
||
GetSurfaceType(
|
||
GroundHit: HitResult,
|
||
AngleThresholdsRads: S_AngleThresholds
|
||
): E_SurfaceType
|
||
```
|
||
|
||
**Характеристики:**
|
||
- ⚠️ Impure (LineTraceByChannel)
|
||
- ✅ Separate snapping logic
|
||
- ✅ Clear condition checking
|
||
|
||
---
|
||
|
||
### 6. BFL_RotationController (Rotation Library)
|
||
|
||
**Роль:** Character rotation toward movement direction
|
||
|
||
**Ключевые методы:**
|
||
|
||
```typescript
|
||
// Calculate target yaw from direction
|
||
CalculateTargetYaw(MovementDirection: Vector): Float
|
||
|
||
// Calculate full target rotation
|
||
CalculateTargetRotation(MovementDirection: Vector): Rotator
|
||
|
||
// Smooth rotation interpolation
|
||
InterpolateRotation(
|
||
CurrentRotation: Rotator,
|
||
TargetRotation: Rotator,
|
||
RotationSpeed: Float,
|
||
DeltaTime: Float,
|
||
MinSpeedForRotation: Float,
|
||
CurrentSpeed: Float
|
||
): S_RotationResult
|
||
|
||
// Convenience method
|
||
UpdateRotation(
|
||
CurrentRotation: Rotator,
|
||
MovementDirection: Vector,
|
||
Config: DA_MovementConfig,
|
||
DeltaTime: Float,
|
||
CurrentSpeed: Float
|
||
): S_RotationResult
|
||
```
|
||
|
||
**Характеристики:**
|
||
- ✅ Pure functions
|
||
- ✅ Wraparound handling (180°/-180°)
|
||
- ✅ Min speed threshold
|
||
|
||
---
|
||
|
||
### 7. BFL_MovementStateMachine (State Machine)
|
||
|
||
**Роль:** Determine movement state from context
|
||
|
||
**Ключевые методы:**
|
||
|
||
```typescript
|
||
// Main state determination
|
||
DetermineState(Context: S_MovementContext): E_MovementState
|
||
|
||
// Internal helpers
|
||
private DetermineAirborneState(Context: S_MovementContext): E_MovementState
|
||
private DetermineGroundedState(Context: S_MovementContext): E_MovementState
|
||
```
|
||
|
||
**State Priority Logic:**
|
||
```
|
||
1. IsGrounded?
|
||
├─ Yes: Check surface type
|
||
│ ├─ SteepSlope → Sliding
|
||
│ ├─ Wall/Ceiling → Blocked
|
||
│ └─ Walkable → Check input
|
||
│ ├─ Has input & speed > 1.0 → Walking
|
||
│ └─ Else → Idle
|
||
└─ No → Airborne
|
||
```
|
||
|
||
**Характеристики:**
|
||
- ✅ Pure FSM logic
|
||
- ✅ Priority-based transitions
|
||
- ✅ Clear state rules
|
||
|
||
---
|
||
|
||
### 8. BFL_SurfaceClassifier (Surface Classification)
|
||
|
||
**Роль:** Classify surface based on normal angle
|
||
|
||
**Ключевые методы:**
|
||
|
||
```typescript
|
||
// Main classification
|
||
Classify(
|
||
SurfaceNormal: Vector,
|
||
AngleThresholdsRads: S_AngleThresholds
|
||
): E_SurfaceType
|
||
|
||
// Type checking helpers
|
||
IsWalkable(surfaceType: E_SurfaceType): boolean
|
||
IsSteep(surfaceType: E_SurfaceType): boolean
|
||
IsWall(surfaceType: E_SurfaceType): boolean
|
||
IsCeiling(surfaceType: E_SurfaceType): boolean
|
||
IsNone(surfaceType: E_SurfaceType): boolean
|
||
```
|
||
|
||
**Classification Rules:**
|
||
```
|
||
Surface Angle → Type
|
||
─────────────────────
|
||
≤ Walkable → Walkable (0°-50°)
|
||
≤ SteepSlope → SteepSlope (50°-85°)
|
||
≤ Wall → Wall (85°-95°)
|
||
> Wall → Ceiling (95°-180°)
|
||
```
|
||
|
||
**Характеристики:**
|
||
- ✅ Pure functions
|
||
- ✅ Angle-based classification
|
||
- ✅ Type-safe queries
|
||
|
||
---
|
||
|
||
## Структуры данных
|
||
|
||
### S_MovementState (Complete State Snapshot)
|
||
|
||
**Роль:** Immutable snapshot всего состояния движения
|
||
|
||
```typescript
|
||
interface S_MovementState {
|
||
// ═══════════════════════════════════════════════════════
|
||
// TRANSFORM
|
||
// ═══════════════════════════════════════════════════════
|
||
Location: Vector // World location
|
||
Rotation: Rotator // Yaw rotation
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// VELOCITY & PHYSICS
|
||
// ═══════════════════════════════════════════════════════
|
||
Velocity: Vector // Current velocity (cm/s)
|
||
Speed: Float // Horizontal speed (cm/s)
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// GROUND STATE
|
||
// ═══════════════════════════════════════════════════════
|
||
IsGrounded: boolean // On walkable ground?
|
||
GroundHit: HitResult // Ground trace result
|
||
SurfaceType: E_SurfaceType // Current surface classification
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// COLLISION STATE
|
||
// ═══════════════════════════════════════════════════════
|
||
IsBlocked: boolean // Blocked by collision?
|
||
CollisionCount: number // Collision checks this frame
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// ROTATION STATE
|
||
// ═══════════════════════════════════════════════════════
|
||
IsRotating: boolean // Currently rotating?
|
||
RotationDelta: Float // Remaining angular distance (degrees)
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// MOVEMENT STATE
|
||
// ═══════════════════════════════════════════════════════
|
||
MovementState: E_MovementState // Current FSM state
|
||
InputMagnitude: Float // Input magnitude (0-1)
|
||
}
|
||
```
|
||
|
||
**Usage Pattern:**
|
||
```typescript
|
||
// Immutable transformation
|
||
const newState = BFL_MovementProcessor.ProcessMovement(
|
||
currentState, // Never modified
|
||
input,
|
||
debugFlag
|
||
);
|
||
|
||
// Apply to actor
|
||
this.GetOwner().SetActorLocation(newState.Location);
|
||
this.GetOwner().SetActorRotation(newState.Rotation);
|
||
|
||
// Store for next frame
|
||
this.CurrentMovementState = newState;
|
||
```
|
||
|
||
---
|
||
|
||
### S_MovementInput (Input Encapsulation)
|
||
|
||
**Роль:** All data needed для movement processing
|
||
|
||
```typescript
|
||
interface S_MovementInput {
|
||
InputVector: Vector // Player input (normalized XY)
|
||
DeltaTime: Float // Frame delta time (seconds)
|
||
CapsuleComponent: CapsuleComponent | null // Collision capsule
|
||
Config: DA_MovementConfig // Movement config
|
||
AngleThresholdsRads: S_AngleThresholds // Surface thresholds (radians)
|
||
}
|
||
```
|
||
|
||
**Usage:**
|
||
```typescript
|
||
const input: S_MovementInput = {
|
||
InputVector: playerInput,
|
||
DeltaTime: deltaTime,
|
||
CapsuleComponent: this.CapsuleComponent,
|
||
Config: this.Config,
|
||
AngleThresholdsRads: this.AngleThresholdsRads
|
||
};
|
||
|
||
const newState = BFL_MovementProcessor.ProcessMovement(
|
||
this.CurrentMovementState,
|
||
input,
|
||
this.DebugHUDComponent?.ShowVisualDebug ?? false
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
### S_MovementContext (State Machine Input)
|
||
|
||
**Роль:** Context для state determination
|
||
|
||
```typescript
|
||
interface S_MovementContext {
|
||
IsGrounded: boolean // On walkable ground?
|
||
SurfaceType: E_SurfaceType // Surface classification
|
||
InputMagnitude: Float // Input strength (0-1)
|
||
CurrentSpeed: Float // Horizontal speed (cm/s)
|
||
VerticalVelocity: Float // Z velocity (cm/s)
|
||
IsBlocked: boolean // Blocked by collision?
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### DA_MovementConfig (Configuration Asset)
|
||
|
||
**Роль:** Centralized movement constants
|
||
|
||
```typescript
|
||
class DA_MovementConfig extends PrimaryDataAsset {
|
||
// ═══════════════════════════════════════════════════════
|
||
// MOVEMENT PHYSICS
|
||
// ═══════════════════════════════════════════════════════
|
||
readonly MaxSpeed: Float = 800.0 // Max horizontal speed (cm/s)
|
||
readonly Acceleration: Float = 10.0 // VInterpTo acceleration rate
|
||
readonly Friction: Float = 8.0 // VInterpTo friction rate
|
||
readonly Gravity: Float = 980.0 // Gravity (cm/s²)
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// SURFACE DETECTION
|
||
// ═══════════════════════════════════════════════════════
|
||
readonly AngleThresholdsDegrees: S_AngleThresholds = {
|
||
Walkable: 50.0, // ≤50° = walkable
|
||
SteepSlope: 85.0, // ≤85° = steep slope
|
||
Wall: 95.0 // ≤95° = wall
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// COLLISION SETTINGS
|
||
// ═══════════════════════════════════════════════════════
|
||
readonly GroundTraceDistance: Float = 50.0 // Ground detection distance
|
||
readonly MinStepSize: Float = 1.0 // Min sweep step size
|
||
readonly MaxStepSize: Float = 50.0 // Max sweep step size
|
||
readonly MaxCollisionChecks: Float = 25 // Max checks per frame
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// CHARACTER ROTATION
|
||
// ═══════════════════════════════════════════════════════
|
||
RotationSpeed: Float = 360.0 // Rotation speed (deg/s)
|
||
MinSpeedForRotation: Float = 50.0 // Min speed to rotate
|
||
ShouldRotateToMovement: boolean = true // Enable rotation
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Enums
|
||
|
||
```typescript
|
||
// Movement FSM states
|
||
enum E_MovementState {
|
||
Idle = 'Idle', // Stationary on ground
|
||
Walking = 'Walking', // Moving on ground
|
||
Airborne = 'Airborne', // In the air
|
||
Sliding = 'Sliding', // Sliding on steep slope
|
||
Blocked = 'Blocked' // Blocked by collision
|
||
}
|
||
|
||
// Surface classification
|
||
enum E_SurfaceType {
|
||
None = 'None', // No ground contact
|
||
Walkable = 'Walkable', // Normal walking ≤50°
|
||
SteepSlope = 'SteepSlope', // Sliding 50°-85°
|
||
Wall = 'Wall', // Collision 85°-95°
|
||
Ceiling = 'Ceiling' // Overhead >95°
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Data Flow Diagram
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ AC_Movement │
|
||
│ (Imperative Shell) │
|
||
├──────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ProcessMovementInput(InputVector, DeltaTime) │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌────────────────────────┐ │
|
||
│ │ Prepare S_MovementInput│ │
|
||
│ └────────────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────────────┐ │
|
||
│ │ BFL_MovementProcessor │ │
|
||
│ │ .ProcessMovement() │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────────────────────┐ │ │
|
||
│ │ │ PHASE 1: Input & Rotation │ │ │
|
||
│ │ │ • Calculate input magnitude │ │ │
|
||
│ │ │ • BFL_RotationController │ │ │
|
||
│ │ └─────────────────────────────┘ │ │
|
||
│ │ │ │ │
|
||
│ │ ┌─────────▼───────────────────┐ │ │
|
||
│ │ │ PHASE 2: Ground Detection │ │ │
|
||
│ │ │ • BFL_GroundProbe │ │ │
|
||
│ │ │ • BFL_SurfaceClassifier │ │ │
|
||
│ │ └─────────────────────────────┘ │ │
|
||
│ │ │ │ │
|
||
│ │ ┌─────────▼───────────────────┐ │ │
|
||
│ │ │ PHASE 3: Physics │ │ │
|
||
│ │ │ • BFL_Kinematics │ │ │
|
||
│ │ │ - Ground velocity / Friction│ │ │
|
||
│ │ │ - Gravity │ │ │
|
||
│ │ └─────────────────────────────┘ │ │
|
||
│ │ │ │ │
|
||
│ │ ┌─────────▼───────────────────┐ │ │
|
||
│ │ │ PHASE 4: Movement (Sweep) │ │ │
|
||
│ │ │ • BFL_CollisionResolver │ │ │
|
||
│ │ │ - Perform sweep │ │ │
|
||
│ │ │ - Calculate slide │ │ │
|
||
│ │ └─────────────────────────────┘ │ │
|
||
│ │ │ │ │
|
||
│ │ ┌─────────▼───────────────────┐ │ │
|
||
│ │ │ PHASE 5: Ground Snapping │ │ │
|
||
│ │ │ • BFL_GroundProbe │ │ │
|
||
│ │ └─────────────────────────────┘ │ │
|
||
│ │ │ │ │
|
||
│ │ ┌─────────▼───────────────────┐ │ │
|
||
│ │ │ PHASE 6: State Determination│ │ │
|
||
│ │ │ • BFL_MovementStateMachine │ │ │
|
||
│ │ └─────────────────────────────┘ │ │
|
||
│ │ │ │ │
|
||
│ │ ┌─────────▼───────────────────┐ │ │
|
||
│ │ │ Return S_MovementState │ │ │
|
||
│ │ └─────────────────────────────┘ │ │
|
||
│ └─────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌────────────────────────┐ │
|
||
│ │ Apply to Actor │ │
|
||
│ │ • SetActorLocation() │ │
|
||
│ │ • SetActorRotation() │ │
|
||
│ └────────────────────────┘ │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Processing Pipeline Details
|
||
|
||
### Phase 1: Input & Rotation
|
||
```typescript
|
||
// Calculate input strength
|
||
const inputMagnitude = VectorLength(InputVector);
|
||
|
||
// Update rotation
|
||
const rotationResult = BFL_RotationController.UpdateRotation(
|
||
CurrentState.Rotation,
|
||
InputVector,
|
||
Config,
|
||
DeltaTime,
|
||
CurrentState.Speed
|
||
);
|
||
```
|
||
|
||
**Output:** `rotationResult.Rotation`, `rotationResult.IsRotating`
|
||
|
||
---
|
||
|
||
### Phase 2: Ground Detection
|
||
```typescript
|
||
// Perform ground trace
|
||
const groundHit = BFL_GroundProbe.CheckGround(
|
||
CurrentState.Location,
|
||
CapsuleComponent,
|
||
AngleThresholdsRads,
|
||
Config
|
||
);
|
||
|
||
// Determine ground state
|
||
const isGrounded = groundHit.BlockingHit;
|
||
|
||
// Classify surface
|
||
const surfaceType = BFL_GroundProbe.GetSurfaceType(
|
||
groundHit,
|
||
AngleThresholdsRads
|
||
);
|
||
```
|
||
|
||
**Output:** `groundHit`, `isGrounded`, `surfaceType`
|
||
|
||
---
|
||
|
||
### Phase 3: Physics Calculation
|
||
```typescript
|
||
let newVelocity = CurrentState.Velocity;
|
||
|
||
// Ground movement OR air friction
|
||
if (IsWalkable(surfaceType) && isGrounded) {
|
||
newVelocity = BFL_Kinematics.CalculateGroundVelocity(
|
||
newVelocity,
|
||
InputVector,
|
||
DeltaTime,
|
||
Config
|
||
);
|
||
} else {
|
||
newVelocity = BFL_Kinematics.CalculateFriction(
|
||
newVelocity,
|
||
DeltaTime,
|
||
Config
|
||
);
|
||
}
|
||
|
||
// Apply gravity
|
||
newVelocity = BFL_Kinematics.CalculateGravity(
|
||
newVelocity,
|
||
isGrounded,
|
||
Config
|
||
);
|
||
|
||
// Calculate speed
|
||
const newSpeed = BFL_Kinematics.GetHorizontalSpeed(newVelocity);
|
||
```
|
||
|
||
**Output:** `newVelocity`, `newSpeed`
|
||
|
||
---
|
||
|
||
### Phase 4: Movement Application (Sweep)
|
||
```typescript
|
||
// Convert velocity to displacement
|
||
const desiredDelta = newVelocity * DeltaTime;
|
||
|
||
// Perform swept collision
|
||
const sweepResult = BFL_CollisionResolver.PerformSweep(
|
||
CurrentState.Location,
|
||
desiredDelta,
|
||
CapsuleComponent,
|
||
Config,
|
||
DeltaTime,
|
||
IsShowVisualDebug
|
||
);
|
||
|
||
let finalLocation = sweepResult.Location;
|
||
|
||
// Handle collision sliding
|
||
if (sweepResult.Blocked) {
|
||
const slideVector = BFL_CollisionResolver.CalculateSlideVector(
|
||
sweepResult,
|
||
desiredDelta,
|
||
CurrentState.Location
|
||
);
|
||
|
||
// Apply slide if valid
|
||
if (VectorLength(slideVector) > 0.5 &&
|
||
Dot(Normal(slideVector), sweepResult.Hit.ImpactNormal) >= -0.1) {
|
||
finalLocation = sweepResult.Location + slideVector;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Output:** `finalLocation`, `sweepResult`
|
||
|
||
---
|
||
|
||
### Phase 5: Ground Snapping
|
||
```typescript
|
||
if (BFL_GroundProbe.ShouldSnapToGround(
|
||
newVelocity.Z,
|
||
groundHit,
|
||
isGrounded
|
||
)) {
|
||
finalLocation = BFL_GroundProbe.CalculateSnapLocation(
|
||
finalLocation,
|
||
groundHit,
|
||
CapsuleComponent,
|
||
Config.GroundTraceDistance
|
||
);
|
||
}
|
||
```
|
||
|
||
**Output:** `finalLocation` (potentially snapped)
|
||
|
||
---
|
||
|
||
### Phase 6: State Determination
|
||
```typescript
|
||
const movementState = BFL_MovementStateMachine.DetermineState({
|
||
IsGrounded: isGrounded,
|
||
SurfaceType: surfaceType,
|
||
InputMagnitude: inputMagnitude,
|
||
CurrentSpeed: newSpeed,
|
||
VerticalVelocity: newVelocity.Z,
|
||
IsBlocked: sweepResult.Blocked
|
||
});
|
||
```
|
||
|
||
**Output:** `movementState` (E_MovementState)
|
||
|
||
---
|
||
|
||
### Final State Construction
|
||
```typescript
|
||
return {
|
||
Location: finalLocation,
|
||
Rotation: rotationResult.Rotation,
|
||
Velocity: newVelocity,
|
||
Speed: newSpeed,
|
||
IsGrounded: isGrounded,
|
||
GroundHit: groundHit,
|
||
SurfaceType: surfaceType,
|
||
IsBlocked: sweepResult.Blocked,
|
||
CollisionCount: sweepResult.CollisionCount,
|
||
IsRotating: rotationResult.IsRotating,
|
||
RotationDelta: rotationResult.RemainingDelta,
|
||
MovementState: movementState,
|
||
InputMagnitude: inputMagnitude
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## API Reference
|
||
|
||
### AC_Movement
|
||
|
||
#### InitializeMovementSystem()
|
||
```typescript
|
||
InitializeMovementSystem(
|
||
CapsuleComponentRef: CapsuleComponent | null = null,
|
||
DebugHUDComponentRef: AC_DebugHUD | null = null
|
||
): void
|
||
```
|
||
|
||
**Описание:** Инициализирует систему движения
|
||
**Эффекты:**
|
||
- Sets `IsInitialized = true`
|
||
- Converts angle thresholds degrees → radians
|
||
- Creates initial movement state
|
||
- Registers debug page if HUD provided
|
||
|
||
**Пример:**
|
||
```typescript
|
||
this.MovementComponent.InitializeMovementSystem(
|
||
this.CharacterCapsule,
|
||
this.DebugHUDComponent
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
#### ProcessMovementInput()
|
||
```typescript
|
||
ProcessMovementInput(
|
||
InputVector: Vector,
|
||
DeltaTime: Float
|
||
): void
|
||
```
|
||
|
||
**Описание:** Main movement processing entry point
|
||
**Параметры:**
|
||
- `InputVector` - Camera-relative movement input (normalized)
|
||
- `DeltaTime` - Frame delta time (seconds)
|
||
|
||
**Flow:**
|
||
1. Constructs S_MovementInput
|
||
2. Calls BFL_MovementProcessor.ProcessMovement()
|
||
3. Applies resulting Location and Rotation to actor
|
||
|
||
**Пример:**
|
||
```typescript
|
||
// In EventTick
|
||
this.MovementComponent.ProcessMovementInput(
|
||
this.CalculateMovementInput(),
|
||
DeltaSeconds
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
### BFL_MovementProcessor
|
||
|
||
#### ProcessMovement()
|
||
```typescript
|
||
ProcessMovement(
|
||
CurrentState: S_MovementState,
|
||
Input: S_MovementInput,
|
||
IsShowVisualDebug: boolean = false
|
||
): S_MovementState
|
||
```
|
||
|
||
**Описание:** Unified movement processing - executes all 6 phases
|
||
**Параметры:**
|
||
- `CurrentState` - Current movement state (immutable)
|
||
- `Input` - Movement input data
|
||
- `IsShowVisualDebug` - Show debug traces in world
|
||
|
||
**Возвращает:** New complete movement state
|
||
|
||
**Purity:** Impure (collision traces), but deterministic
|
||
|
||
**Пример:**
|
||
```typescript
|
||
const newState = BFL_MovementProcessor.ProcessMovement(
|
||
this.CurrentMovementState,
|
||
{
|
||
InputVector: inputVector,
|
||
DeltaTime: deltaTime,
|
||
CapsuleComponent: this.CapsuleComponent,
|
||
Config: this.Config,
|
||
AngleThresholdsRads: this.AngleThresholdsRads
|
||
},
|
||
this.ShowDebug
|
||
);
|
||
|
||
// Apply results
|
||
this.GetOwner().SetActorLocation(newState.Location);
|
||
this.GetOwner().SetActorRotation(newState.Rotation);
|
||
this.CurrentMovementState = newState;
|
||
```
|
||
|
||
---
|
||
|
||
#### CreateInitialState()
|
||
```typescript
|
||
CreateInitialState(
|
||
Location: Vector,
|
||
Rotation: Rotator
|
||
): S_MovementState
|
||
```
|
||
|
||
**Описание:** Creates initial movement state with defaults
|
||
**Purity:** Pure
|
||
|
||
**Пример:**
|
||
```typescript
|
||
this.CurrentMovementState = BFL_MovementProcessor.CreateInitialState(
|
||
this.GetOwner().GetActorLocation(),
|
||
this.GetOwner().GetActorRotation()
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## Best Practices
|
||
|
||
### Initialization
|
||
```typescript
|
||
// ✅ Good - proper initialization order
|
||
class BP_MainCharacter extends Character {
|
||
private MovementComponent: AC_Movement;
|
||
|
||
ReceiveBeginPlay(): void {
|
||
this.MovementComponent.InitializeMovementSystem(
|
||
this.GetCapsuleComponent(),
|
||
this.DebugHUDComponent
|
||
);
|
||
}
|
||
}
|
||
|
||
// ❌ Bad - using before initialization
|
||
ReceiveBeginPlay(): void {
|
||
this.MovementComponent.ProcessMovementInput(input, dt); // Will early-return!
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### State Access
|
||
```typescript
|
||
// ✅ Good - direct state access
|
||
const currentSpeed = this.MovementComponent.CurrentMovementState.Speed;
|
||
const isGrounded = this.MovementComponent.CurrentMovementState.IsGrounded;
|
||
|
||
// 🔄 Alternative - expose via getter
|
||
class AC_Movement {
|
||
public GetMovementState(): S_MovementState {
|
||
return this.CurrentMovementState;
|
||
}
|
||
}
|
||
|
||
const state = this.MovementComponent.GetMovementState();
|
||
const speed = state.Speed;
|
||
```
|
||
|
||
---
|
||
|
||
### Testing
|
||
```typescript
|
||
// ✅ Good - test processor directly
|
||
describe('BFL_MovementProcessor', () => {
|
||
it('should accelerate on ground', () => {
|
||
const initialState = BFL_MovementProcessor.CreateInitialState(
|
||
new Vector(0, 0, 100),
|
||
new Rotator(0, 0, 0)
|
||
);
|
||
|
||
const input: S_MovementInput = {
|
||
InputVector: new Vector(1, 0, 0),
|
||
DeltaTime: 0.016,
|
||
CapsuleComponent: mockCapsule,
|
||
Config: testConfig,
|
||
AngleThresholdsRads: testThresholds
|
||
};
|
||
|
||
const newState = BFL_MovementProcessor.ProcessMovement(
|
||
initialState,
|
||
input,
|
||
false
|
||
);
|
||
|
||
expect(newState.Velocity.X).toBeGreaterThan(0);
|
||
expect(newState.MovementState).toBe(E_MovementState.Walking);
|
||
});
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### Performance
|
||
```typescript
|
||
// ✅ Good - check initialization once
|
||
if (this.MovementComponent.IsInitialized) {
|
||
// Movement processing
|
||
}
|
||
|
||
// ✅ Good - cache config access
|
||
const maxSpeed = this.Config.MaxSpeed;
|
||
for (let i = 0; i < 100; i++) {
|
||
// Use maxSpeed
|
||
}
|
||
|
||
// ❌ Bad - repeated property access
|
||
for (let i = 0; i < 100; i++) {
|
||
if (speed > this.Config.MaxSpeed) { ... }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Configuration Guidelines
|
||
|
||
### Movement Physics
|
||
| Parameter | Default | Description | Tuning Guide |
|
||
|-----------|---------|-------------|--------------|
|
||
| MaxSpeed | 800.0 | Max horizontal speed (cm/s) | Higher = faster character |
|
||
| Acceleration | 10.0 | VInterpTo acceleration rate | Higher = more responsive |
|
||
| Friction | 8.0 | VInterpTo friction rate | Higher = faster stopping |
|
||
| Gravity | 980.0 | Gravity force (cm/s²) | Standard Earth gravity |
|
||
|
||
### Surface Detection
|
||
| Parameter | Default | Description |
|
||
|-----------|---------|-------------|
|
||
| Walkable | 50° | Max walkable angle |
|
||
| SteepSlope | 85° | Max steep slope angle |
|
||
| Wall | 95° | Max wall angle |
|
||
|
||
### Collision
|
||
| Parameter | Default | Description | Performance Impact |
|
||
|-----------|---------|-------------|-------------------|
|
||
| MinStepSize | 1.0 | Min sweep step (cm) | Smaller = more precise, slower |
|
||
| MaxStepSize | 50.0 | Max sweep step (cm) | Larger = faster, less precise |
|
||
| MaxCollisionChecks | 25 | Max checks per frame | Higher = safer, more expensive |
|
||
| GroundTraceDistance | 50.0 | Ground detection distance (cm) | Balance precision vs cost |
|
||
|
||
### Rotation
|
||
| Parameter | Default | Description |
|
||
|-----------|---------|-------------|
|
||
| RotationSpeed | 360.0 | Rotation speed (deg/s) |
|
||
| MinSpeedForRotation | 50.0 | Min speed to rotate (cm/s) |
|
||
| ShouldRotateToMovement | true | Enable rotation |
|
||
|
||
---
|
||
|
||
## Performance Characteristics
|
||
|
||
### Frame Budget
|
||
- **Target:** <1ms per frame (60 FPS)
|
||
- **Typical:** 0.3-0.7ms
|
||
- **Max:** ~1.5ms (complex collision scenarios)
|
||
|
||
### Bottlenecks
|
||
1. **Swept Collision** (most expensive)
|
||
- Multiple CapsuleTraceByChannel calls
|
||
- Adaptive stepping helps
|
||
- Monitor `CollisionCount` in debug HUD
|
||
|
||
2. **Ground Detection** (moderate)
|
||
- Single LineTraceByChannel per frame
|
||
- Always runs (even airborne)
|
||
|
||
3. **Physics Calculations** (cheap)
|
||
- Pure math operations
|
||
- VInterpTo is optimized
|
||
|
||
### Optimization Tips
|
||
1. Increase `MinStepSize` if collision checks too high
|
||
2. Decrease `GroundTraceDistance` to minimum needed
|
||
3. Disable `ShouldRotateToMovement` for static NPCs
|
||
4. Use `IsShowVisualDebug = false` in production
|
||
|
||
---
|
||
|
||
## Testing Strategy
|
||
|
||
### Unit Testing (BFL_* modules)
|
||
```typescript
|
||
describe('BFL_Kinematics', () => {
|
||
it('should apply friction correctly', () => {
|
||
const velocity = new Vector(800, 0, 0);
|
||
const result = BFL_Kinematics.CalculateFriction(
|
||
velocity,
|
||
0.016,
|
||
testConfig
|
||
);
|
||
|
||
expect(result.X).toBeLessThan(velocity.X);
|
||
expect(result.Z).toBe(0);
|
||
});
|
||
});
|
||
```
|
||
|
||
### Integration Testing (BFL_MovementProcessor)
|
||
```typescript
|
||
describe('Movement Pipeline', () => {
|
||
it('should process complete frame', () => {
|
||
const state = processFullFrame(initialState, input);
|
||
|
||
expect(state.Location).not.toEqual(initialState.Location);
|
||
expect(state.Velocity).toBeDefined();
|
||
expect(state.MovementState).toBeDefined();
|
||
});
|
||
});
|
||
```
|
||
|
||
### Manual Testing (see ManualTestingChecklist.md)
|
||
- Ground detection
|
||
- Surface classification
|
||
- Collision response
|
||
- Rotation behavior
|
||
- Debug visualization
|
||
|
||
---
|
||
|
||
## Known Limitations
|
||
|
||
### Current Constraints
|
||
1. **Binary Ground State:** No partial contact detection
|
||
2. **Single Collision Shape:** Capsule only
|
||
3. **Frame-Dependent Stepping:** Sweep precision varies with framerate
|
||
4. **No Material Physics:** Surface material not considered
|
||
5. **Simple Sliding:** Basic projection, no advanced friction
|
||
|
||
### By Design
|
||
1. **Capsule Component Required:** System assumes capsule collision
|
||
2. **Deterministic Traces:** Relies on UE physics determinism
|
||
3. **Horizontal Focus:** Optimized for ground-based movement
|
||
4. **No Network Code:** Not yet optimized for multiplayer
|
||
|
||
---
|
||
|
||
## Future Extensions (Stage 10+)
|
||
|
||
### Planned Features
|
||
- **Jump System:** Vertical velocity application, coyote time, jump buffering
|
||
- **Steep Slope Sliding:** Physics-based sliding on non-walkable surfaces
|
||
- **Moving Platforms:** Platform attachment and relative movement
|
||
- **Wall Running:** Advanced surface interaction
|
||
- **Ledge Detection:** Edge detection for platforming
|
||
- **Material-Based Physics:** Surface material awareness
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
```
|
||
Content/
|
||
├── Movement/
|
||
│ ├── Components/
|
||
│ │ └── AC_Movement.ts # Imperative shell (~230 LOC)
|
||
│ ├── Core/
|
||
│ │ ├── BFL_MovementProcessor.ts # Functional core (~260 LOC)
|
||
│ │ ├── DA_MovementConfig.ts # Configuration asset
|
||
│ │ ├── DA_MovementConfigDefault.ts # Default config
|
||
│ │ ├── E_MovementState.ts # Movement states enum
|
||
│ │ ├── S_MovementInput.ts # Input structure
|
||
│ │ └── S_MovementState.ts # State structure
|
||
│ ├── Collision/
|
||
│ │ ├── BFL_CollisionResolver.ts # Swept collision
|
||
│ │ ├── BFL_GroundProbe.ts # Ground detection
|
||
│ │ └── S_SweepResult.ts # Sweep result structure
|
||
│ ├── Physics/
|
||
│ │ └── BFL_Kinematics.ts # Movement physics
|
||
│ ├── Rotation/
|
||
│ │ ├── BFL_RotationController.ts # Rotation logic
|
||
│ │ └── S_RotationResult.ts # Rotation result structure
|
||
│ ├── State/
|
||
│ │ ├── BFL_MovementStateMachine.ts # FSM logic
|
||
│ │ └── S_MovementContext.ts # State context structure
|
||
│ ├── Surface/
|
||
│ │ ├── BFL_SurfaceClassifier.ts # Surface classification
|
||
│ │ ├── E_SurfaceType.ts # Surface types enum
|
||
│ │ └── S_AngleThresholds.ts # Angle thresholds structure
|
||
│ └── Documentation/
|
||
│ ├── TDD.md # This document
|
||
│ └── ManualTestingChecklist.md # Testing procedures
|
||
└── Math/
|
||
└── Libraries/
|
||
└── BFL_Vectors.ts # Vector math utilities
|
||
```
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### Character Falling Through Ground
|
||
**Symptoms:** Character drops through walkable surface
|
||
**Checks:**
|
||
1. `GroundTraceDistance > 0`
|
||
2. Ground has `Visibility` collision channel
|
||
3. `CapsuleComponent` initialized correctly
|
||
4. `IsInitialized == true`
|
||
|
||
**Debug:**
|
||
```typescript
|
||
// Enable visual debug
|
||
this.DebugHUDComponent.ShowVisualDebug = true;
|
||
// Check ground hit in debug HUD
|
||
// Look for green line trace downward
|
||
```
|
||
|
||
---
|
||
|
||
### Excessive Collision Checks
|
||
**Symptoms:** `CollisionCount` consistently hitting `MaxCollisionChecks`
|
||
**Fixes:**
|
||
1. Increase `MaxStepSize` (careful: less precision)
|
||
2. Decrease `MaxSpeed`
|
||
3. Increase `MaxCollisionChecks` (careful: performance cost)
|
||
4. Check for collision geometry issues
|
||
|
||
**Debug:**
|
||
```typescript
|
||
// Monitor in debug HUD
|
||
CollisionCount / MaxCollisionChecks
|
||
// Should be <50% most frames
|
||
```
|
||
|
||
---
|
||
|
||
### Jittery Z Position
|
||
**Symptoms:** Character bouncing up/down slightly
|
||
**Fixes:**
|
||
1. Verify ground detection working (`IsGrounded` in debug HUD)
|
||
2. Check ground snapping active
|
||
3. Slightly increase `GroundTraceDistance`
|
||
4. Verify ground has consistent collision
|
||
|
||
---
|
||
|
||
### Character Not Rotating
|
||
**Symptoms:** Character faces wrong direction
|
||
**Checks:**
|
||
1. `Config.ShouldRotateToMovement == true`
|
||
2. `CurrentSpeed > Config.MinSpeedForRotation`
|
||
3. `SetActorRotation()` called each frame
|
||
4. Input vector not zero
|
||
|
||
**Debug:**
|
||
```typescript
|
||
// Check in debug HUD
|
||
Is Rotating: true/false
|
||
Rotation Delta: [degrees remaining]
|
||
Current Yaw: [current angle]
|
||
```
|
||
|
||
---
|
||
|
||
## Conclusion
|
||
|
||
Movement System представляет собой production-ready систему с следующими характеристиками:
|
||
|
||
### Архитектурные достижения ✅
|
||
- **Functional Core, Imperative Shell** pattern реализован полностью
|
||
- **Clear separation of concerns** между subsystems
|
||
- **Pipeline processing** с явными фазами
|
||
- **Immutable state transformations** везде где возможно
|
||
- **Testable design** благодаря pure function libraries
|
||
|
||
### Production Readiness ✅
|
||
- **Deterministic physics** с VInterpTo
|
||
- **Tunneling protection** через swept collision
|
||
- **Frame-rate independence** через DeltaTime
|
||
- **Performance optimized** (<1ms typical frame time)
|
||
- **Debug visualization** comprehensive
|
||
- **Extensible architecture** для future features
|
||
|
||
### Code Quality ✅
|
||
- **LOC reduced** 62% в AC_Movement (600 → 230)
|
||
- **Modularity** high - 8 focused modules
|
||
- **Documentation** comprehensive
|
||
- **Type safety** strong через TypeScript structs
|
||
|
||
**Production Status:** ✅ **Ready for Stage 10**
|
||
|
||
Архитектура готова для расширения Jump System и последующих features без major refactoring.
|