// 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 { Vector } from '#root/UE/Vector.ts'; /** * Movement System Component * Core deterministic movement system for 3D platformer * Handles surface classification and movement physics calculations */ export class AC_Movement extends ActorComponent { // ════════════════════════════════════════════════════════════════════════════════════════ // FUNCTIONS // ════════════════════════════════════════════════════════════════════════════════════════ /** * Classify surface type based on normal vector * @param SurfaceNormal - Normalized surface normal vector * @param AngleThresholds - Angle thresholds in radians * @returns Surface type classification * @example * // Classify flat ground * ClassifySurface(new Vector(0,0,1), thresholds) // returns E_SurfaceType.Walkable * @pure true * @category Surface Detection */ public ClassifySurface( SurfaceNormal: Vector, AngleThresholds: S_AngleThresholds ): E_SurfaceType { const SurfaceAngle = BFL_Vectors.GetSurfaceAngle(SurfaceNormal); /** * Check if angle is within walkable range */ const IsWalkableAngle = (walkableAngle: Float): boolean => SurfaceAngle <= walkableAngle; /** * Check if angle is within steep slope range */ const IsSteepSlopeAngle = (steepSlopeAngle: Float): boolean => SurfaceAngle <= steepSlopeAngle; /** * Check if angle is within wall range */ const IsWallAngle = (wallAngle: Float): boolean => SurfaceAngle <= wallAngle; if (IsWalkableAngle(AngleThresholds.Walkable)) { return E_SurfaceType.Walkable; } else if (IsSteepSlopeAngle(AngleThresholds.SteepSlope)) { return E_SurfaceType.SteepSlope; } else if (IsWallAngle(AngleThresholds.Wall)) { return E_SurfaceType.Wall; } else { return E_SurfaceType.Ceiling; } } /** * Check if surface allows normal walking movement * @param SurfaceType - Surface type to check * @returns True if surface is walkable * @pure true * @category Surface Detection */ private IsSurfaceWalkable(SurfaceType: E_SurfaceType): boolean { return SurfaceType === E_SurfaceType.Walkable; } /** * Check if surface causes sliding behavior * @param SurfaceType - Surface type to check * @returns True if surface is steep slope * @pure true * @category Surface Detection */ private IsSurfaceSteep(SurfaceType: E_SurfaceType): boolean { return SurfaceType === E_SurfaceType.SteepSlope; } /** * Check if surface blocks movement (collision) * @param SurfaceType - Surface type to check * @returns True if surface is a wall * @pure true * @category Surface Detection */ private IsSurfaceWall(SurfaceType: E_SurfaceType): boolean { return SurfaceType === E_SurfaceType.Wall; } /** * Check if surface is overhead (ceiling) * @param SurfaceType - Surface type to check * @returns True if surface is ceiling * @pure true * @category Surface Detection */ private IsSurfaceCeiling(SurfaceType: E_SurfaceType): boolean { return SurfaceType === E_SurfaceType.Ceiling; } /** * Check if no surface detected (airborne state) * @param SurfaceType - Surface type to check * @returns True if no surface contact * @pure true * @category Surface Detection */ private IsSurfaceNone(SurfaceType: E_SurfaceType): boolean { 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 * @category System */ public InitializeMovementSystem(): void { this.IsInitialized = true; this.AngleThresholdsRads = { Walkable: MathLibrary.DegreesToRadians( this.AngleThresholdsDegrees.Walkable ), SteepSlope: MathLibrary.DegreesToRadians( this.AngleThresholdsDegrees.SteepSlope ), Wall: MathLibrary.DegreesToRadians(this.AngleThresholdsDegrees.Wall), }; } // ════════════════════════════════════════════════════════════════════════════════════════ // VARIABLES // ════════════════════════════════════════════════════════════════════════════════════════ /** * Movement physics constants * Controls speed, acceleration, friction, and gravity values * @category Movement Config * @instanceEditable true */ public readonly MovementConstants: S_MovementConstants = { MaxSpeed: 600.0, Acceleration: 10.0, Friction: 8.0, Gravity: 980.0, }; /** * Surface classification angle thresholds in degrees * Walkable ≤50°, SteepSlope ≤85°, Wall ≤95°, Ceiling >95° * @category Movement Config * @instanceEditable true */ public readonly AngleThresholdsDegrees: S_AngleThresholds = { Walkable: 50.0, SteepSlope: 85.0, Wall: 95.0, }; /** * Runtime cached angle thresholds in radians * Converted from degrees during initialization for performance * @category Internal Cache */ private AngleThresholdsRads: S_AngleThresholds = { Walkable: 0.0, SteepSlope: 0.0, Wall: 0.0, }; /** * Flag indicating if movement system has been initialized * Ensures angle thresholds are converted before use * @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; }