// Source/TengriPlatformer/Movement/TengriMovementComponent.h #pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "TengriPlatformer/Movement/Core/TengriMovementConfig.h" #include "TengriMovementComponent.generated.h" class UCapsuleComponent; /** * Custom movement component for deterministic 3D platformer physics. * Uses fixed timestep physics with interpolated rendering. * * Architecture: * - Physics State: Updated at fixed rate (default 120Hz) for determinism * - Render State: Interpolated between physics states for smooth visuals * - Accumulator: Manages variable frame delta accumulation */ UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) class TENGRIPLATFORMER_API UTengriMovementComponent : public UActorComponent { GENERATED_BODY() public: UTengriMovementComponent(); protected: virtual void BeginPlay() override; public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; // ======================================================================== // BLUEPRINT API // ======================================================================== /** * Sets the movement input vector. Call every frame from PlayerController. * @param NewInput - Raw input (will be clamped to 1.0 and Z zeroed) */ UFUNCTION(BlueprintCallable, Category = "Tengri Movement") void SetInputVector(FVector NewInput); // ======================================================================== // CONFIGURATION // ======================================================================== /** Movement parameters data asset */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tengri Movement") TObjectPtr MovementConfig; // ======================================================================== // RUNTIME STATE // ======================================================================== /** Current velocity in cm/s (synced from PhysicsVelocity each frame) */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tengri Movement|State") FVector Velocity; /** True when character is on walkable ground */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tengri Movement|State") bool bIsGrounded = false; /** Cached surface thresholds (cosines) for performance */ UPROPERTY(VisibleAnywhere, Category = "Tengri Movement|Debug") FSurfaceThresholds CachedThresholds; private: // ======================================================================== // PHYSICS STATE (Deterministic) // ======================================================================== /** Physics location updated at fixed timestep */ FVector PhysicsLocation = FVector::ZeroVector; /** Physics rotation updated at fixed timestep */ FRotator PhysicsRotation = FRotator::ZeroRotator; /** Physics velocity in cm/s */ FVector PhysicsVelocity = FVector::ZeroVector; // ======================================================================== // RENDER STATE (Interpolated) // ======================================================================== /** Interpolated location for smooth rendering */ FVector RenderLocation = FVector::ZeroVector; /** Interpolated rotation for smooth rendering */ FRotator RenderRotation = FRotator::ZeroRotator; // ======================================================================== // FIXED TIMESTEP // ======================================================================== /** Fixed timestep duration in seconds (calculated from PhysicsTickRate) */ float FixedTimeStep = 1.0f / 120.0f; /** Accumulated variable frame time */ float TimeAccumulator = 0.0f; /** Maximum accumulator value to prevent spiral of death */ float MaxAccumulatorTime = 0.1f; // ======================================================================== // INTERPOLATION HISTORY // ======================================================================== /** Previous physics location for interpolation */ FVector PreviousPhysicsLocation = FVector::ZeroVector; /** Previous physics rotation for interpolation */ FRotator PreviousPhysicsRotation = FRotator::ZeroRotator; // ======================================================================== // INTERNAL STATE // ======================================================================== UPROPERTY() TObjectPtr OwnerCapsule; /** Normalized input vector (Z always 0) */ FVector InputVector = FVector::ZeroVector; // ======================================================================== // INITIALIZATION // ======================================================================== void InitializeSystem(); // ======================================================================== // PHYSICS TICK // ======================================================================== /** * Deterministic physics update at fixed timestep. * All movement logic runs here with constant delta time. * @param FixedDeltaTime - Fixed timestep duration (e.g., 1/120 sec) */ void TickPhysics(float FixedDeltaTime); /** Save current physics state before next physics step */ void SavePreviousPhysicsState(); // ======================================================================== // INTERPOLATION // ======================================================================== /** * Interpolate render state between previous and current physics states. * @param Alpha - Interpolation factor [0..1] */ void InterpolateRenderState(float Alpha); /** Apply interpolated render state to actor transform */ void ApplyRenderState() const; // ======================================================================== // PHYSICS HELPERS // ======================================================================== /** * Snap to ground to prevent slope jitter. * @param InOutLocation - Physics location (modified if snap succeeds) * @param OutSnapHit - Hit result if ground found * @return True if snapped to ground */ bool PerformGroundSnapping(FVector& InOutLocation, FHitResult& OutSnapHit) const; };