41 KiB
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
-
Functional Core, Imperative Shell
- Pure business logic в BFL_* модулях
- Imperative framework integration в AC_Movement
-
Immutable State Transformation
S_MovementState → ProcessMovement() → S_MovementState- Никогда не мутируем входящее состояние
-
Pipeline Processing
- Четкие последовательные фазы обработки
- Каждая фаза читает результаты предыдущей
-
Separation of Concerns
- AC_Movement: координация и framework integration
- BFL_MovementProcessor: бизнес-логика движения
- BFL_*: специализированные подсистемы
-
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
Ключевые методы:
// Инициализация системы
InitializeMovementSystem(
CapsuleComponentRef: CapsuleComponent | null,
DebugHUDComponentRef: AC_DebugHUD | null
): void
// Обработка движения (главная точка входа)
ProcessMovementInput(
InputVector: Vector,
DeltaTime: Float
): void
// Debug
UpdateDebugPage(): void
Приватные поля:
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
Главный метод:
ProcessMovement(
CurrentState: S_MovementState,
Input: S_MovementInput,
IsShowVisualDebug: boolean = false
): S_MovementState
Processing Pipeline (6 фаз):
// 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
Вспомогательные методы:
CreateInitialState(
Location: Vector,
Rotation: Rotator
): S_MovementState
Purity: Impure (из-за collision traces), но deterministic
Размер: ~260 LOC
3. BFL_Kinematics (Physics Library)
Роль: Pure physics calculations для движения
Ключевые методы:
// 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
Ключевые методы:
// Главный 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
Ключевые методы:
// 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
Ключевые методы:
// 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
Ключевые методы:
// 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
Ключевые методы:
// 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 всего состояния движения
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:
// 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
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:
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
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
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
// 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
// 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
// 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
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)
// 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
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
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
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()
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
Пример:
this.MovementComponent.InitializeMovementSystem(
this.CharacterCapsule,
this.DebugHUDComponent
);
ProcessMovementInput()
ProcessMovementInput(
InputVector: Vector,
DeltaTime: Float
): void
Описание: Main movement processing entry point
Параметры:
InputVector- Camera-relative movement input (normalized)DeltaTime- Frame delta time (seconds)
Flow:
- Constructs S_MovementInput
- Calls BFL_MovementProcessor.ProcessMovement()
- Applies resulting Location and Rotation to actor
Пример:
// In EventTick
this.MovementComponent.ProcessMovementInput(
this.CalculateMovementInput(),
DeltaSeconds
);
BFL_MovementProcessor
ProcessMovement()
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 dataIsShowVisualDebug- Show debug traces in world
Возвращает: New complete movement state
Purity: Impure (collision traces), but deterministic
Пример:
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()
CreateInitialState(
Location: Vector,
Rotation: Rotator
): S_MovementState
Описание: Creates initial movement state with defaults
Purity: Pure
Пример:
this.CurrentMovementState = BFL_MovementProcessor.CreateInitialState(
this.GetOwner().GetActorLocation(),
this.GetOwner().GetActorRotation()
);
Best Practices
Initialization
// ✅ 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
// ✅ 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
// ✅ 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
// ✅ 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
-
Swept Collision (most expensive)
- Multiple CapsuleTraceByChannel calls
- Adaptive stepping helps
- Monitor
CollisionCountin debug HUD
-
Ground Detection (moderate)
- Single LineTraceByChannel per frame
- Always runs (even airborne)
-
Physics Calculations (cheap)
- Pure math operations
- VInterpTo is optimized
Optimization Tips
- Increase
MinStepSizeif collision checks too high - Decrease
GroundTraceDistanceto minimum needed - Disable
ShouldRotateToMovementfor static NPCs - Use
IsShowVisualDebug = falsein production
Testing Strategy
Unit Testing (BFL_* modules)
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)
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
- Binary Ground State: No partial contact detection
- Single Collision Shape: Capsule only
- Frame-Dependent Stepping: Sweep precision varies with framerate
- No Material Physics: Surface material not considered
- Simple Sliding: Basic projection, no advanced friction
By Design
- Capsule Component Required: System assumes capsule collision
- Deterministic Traces: Relies on UE physics determinism
- Horizontal Focus: Optimized for ground-based movement
- 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:
GroundTraceDistance > 0- Ground has
Visibilitycollision channel CapsuleComponentinitialized correctlyIsInitialized == true
Debug:
// 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:
- Increase
MaxStepSize(careful: less precision) - Decrease
MaxSpeed - Increase
MaxCollisionChecks(careful: performance cost) - Check for collision geometry issues
Debug:
// Monitor in debug HUD
CollisionCount / MaxCollisionChecks
// Should be <50% most frames
Jittery Z Position
Symptoms: Character bouncing up/down slightly
Fixes:
- Verify ground detection working (
IsGroundedin debug HUD) - Check ground snapping active
- Slightly increase
GroundTraceDistance - Verify ground has consistent collision
Character Not Rotating
Symptoms: Character faces wrong direction
Checks:
Config.ShouldRotateToMovement == trueCurrentSpeed > Config.MinSpeedForRotationSetActorRotation()called each frame- Input vector not zero
Debug:
// 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.