219 lines
8.2 KiB
C++
219 lines
8.2 KiB
C++
// Request Games © All rights reserved
|
|
|
|
// 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;
|
|
struct FPendingLandingEvent; // Forward declaration
|
|
|
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTengriLandingSignature, bool, bIsHeavy);
|
|
|
|
/**
|
|
* 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
|
|
* - Event Accumulation: Events during physics loop are broadcast after completion
|
|
*/
|
|
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
|
|
class TENGRIPLATFORMER_API UTengriMovementComponent : public UActorComponent
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
UTengriMovementComponent();
|
|
|
|
/** Get interpolated render location */
|
|
UFUNCTION(BlueprintPure, Category = "Tengri Movement")
|
|
FVector GetRenderLocation() const { return RenderLocation; }
|
|
|
|
/** Event triggered when character lands (broadcast after physics loop completes) */
|
|
UPROPERTY(BlueprintAssignable, Category = "Tengri Movement|Events")
|
|
FOnTengriLandingSignature OnLanded;
|
|
|
|
/** Instantly snaps character rotation to a new value (bypassing interpolation) */
|
|
void ForceRotation(const FRotator& NewRotation);
|
|
|
|
/** Enable strafe mode (character rotates toward camera instead of movement direction) */
|
|
void SetStrafing(const bool bEnabled) { bStrafing = bEnabled; }
|
|
|
|
protected:
|
|
virtual void BeginPlay() override;
|
|
|
|
/** Strafe mode flag */
|
|
bool bStrafing = false;
|
|
|
|
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);
|
|
|
|
/**
|
|
* Updates jump input state. Call from PlayerController.
|
|
* @param bPressed - True if button just pressed or currently held
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Tengri Movement")
|
|
void SetJumpInput(bool bPressed);
|
|
|
|
// ========================================================================
|
|
// CONFIGURATION
|
|
// ========================================================================
|
|
|
|
/** Movement parameters data asset */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tengri Movement")
|
|
TObjectPtr<UTengriMovementConfig> 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<UCapsuleComponent> OwnerCapsule;
|
|
|
|
/** Normalized input vector (Z always 0) */
|
|
FVector InputVector = FVector::ZeroVector;
|
|
|
|
// ========================================================================
|
|
// JUMP STATE (Internal)
|
|
// ========================================================================
|
|
|
|
/** Timer for Jump Buffering. > 0 means jump was recently pressed. */
|
|
float JumpBufferTimer = 0.0f;
|
|
|
|
/** Timer for Coyote Time. > 0 means we can still jump even if in air. */
|
|
float CoyoteTimer = 0.0f;
|
|
|
|
/** True if jump button is currently held down (for variable jump height) */
|
|
bool bIsJumpHeld = false;
|
|
|
|
/** Flag to prevent Coyote Time reactivation immediately after jumping */
|
|
bool bHasJumpedThisFrame = false;
|
|
|
|
// ========================================================================
|
|
// INITIALIZATION
|
|
// ========================================================================
|
|
|
|
void InitializeSystem();
|
|
|
|
// ========================================================================
|
|
// PHYSICS TICK (UPDATED)
|
|
// ========================================================================
|
|
|
|
/**
|
|
* 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)
|
|
* @param OutPendingLandings - Accumulator for landing events (broadcast later)
|
|
*/
|
|
void TickPhysics(float FixedDeltaTime, TArray<FPendingLandingEvent>& OutPendingLandings);
|
|
|
|
/** 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;
|
|
}; |