Get on Fab →

Introduction

OmniWalk is a high-performance C++ locomotion system for Unreal Engine 5.4+ that enables true arbitrary-surface traversal. It solves the three core problems of magneboot movement: surface detection, gravity direction, and stable camera control. The result is predictable walking on walls, ceilings, spheres, and complex interiors without world-axis constraints.

Native C++ Core

Runs in TG_PrePhysics with tight integration into UCharacterMovementComponent.

Gimbal-Free Camera

Custom controller and camera manager keep mouselook stable across 180-degree transitions.

Surface Adhesion

Multi-point traces, wall detection, and adhesion impulses keep you locked to geometry.

System Map

Surface Detect Gravity Align Camera Solve Input Plane Trace Quat View Move

The runtime loop maps surface normals into a stable gravity plane, then aligns camera and input to that plane.

Gravity Plane Model

OmniWalk defines a new local "Up" based on the surface normal. Movement and camera forward are projected onto this plane every frame to prevent drift and gimbal inversions.

The character basis is rebuilt using quaternions, keeping controls consistent even at 90° and 180° transitions.

UP (Normal) GRAVITY_DIR

Quick Start

This is the shortest reliable path to working wall-walk movement. It assumes a standard UE Character.

Add UOmniWalkPro Set Controller Map Input

Step 1 — Add the Component

Add UOmniWalkPro to your Character (Blueprint or C++). This is the surface/adhesion engine.

Step 2 — Use the OmniWalk Controller

Set your PlayerController to AOmniWalkPlayerController and ensure the camera uses the OmniWalk camera manager.

Step 3 — Map Input on the Gravity Plane

Use the helper node or the controller function below. Do not map input twice.

// C++ example (Character)
AOmniWalkPlayerController* PC = Cast<AOmniWalkPlayerController>(GetController());
if (PC)
{
    const FVector Local(ForwardAxis, RightAxis, 0.0f);
    const FVector World = PC->GetGravityRelativeDirection(Local);
    AddMovementInput(World, 1.0f);
}

Step 4 — Dismount Input (Optional)

Bind a Dismount action and call RequestDismount. OmniWalk can auto-bind for you via bAutoBindDismountInput, and Enhanced Input users can assign a mapping context + action asset.

Checklist
  • Character has UOmniWalkPro component.
  • Controller is AOmniWalkPlayerController.
  • Input mapped using helper node or controller function.
  • bUseInternalInputFix disabled when using helper node.

Character Setup

There are two ways to add OmniWalk to your third-person character. Choose the approach that best fits your project structure.

1

Inherit from AOmniWalkableCharacter

The easiest path. Your character inherits from OmniWalk's base class with everything pre-configured.

  • ✓ Minimal setup required
  • ✓ All interface methods implemented
  • ✓ Best for new characters
2

Implement IOmniWalkable Interface

Add OmniWalk to an existing character by implementing the interface directly.

  • ✓ Keeps existing inheritance
  • ✓ Works with custom character bases
  • ✓ Best for established projects

Inheritance Structure

Approach 1: Inheritance ACharacter AOmniWalkableCharacter AMyThirdPersonCharacter Approach 2: Interface ACharacter AMyExistingCharacter + IOmniWalkable implements interface methods

Approach 1: Inherit from AOmniWalkableCharacter

This is the recommended path for new characters. You inherit from AOmniWalkableCharacter (which itself inherits from ACharacter) and get all the interface implementations for free.

Recommended Best for new projects or when you can change your character's parent class

Step 1: Create Your Character Header

// MyThirdPersonCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "OmniWalkableCharacter.h"  // Instead of ACharacter
#include "MyThirdPersonCharacter.generated.h"

UCLASS()
class MYGAME_API AMyThirdPersonCharacter : public AOmniWalkableCharacter
{
    GENERATED_BODY()

public:
    AMyThirdPersonCharacter();

protected:
    virtual void BeginPlay() override;
    
    // Optional: Implement interface callbacks for custom behavior
    virtual void OnOmniWalkSurfaceAttached_Implementation(
        const FVector& SurfaceNormal) override;
    virtual void OnOmniWalkSurfaceDetached_Implementation() override;
    
    UPROPERTY(VisibleAnywhere, Category = "Camera")
    USpringArmComponent* CameraBoom;
    
    UPROPERTY(VisibleAnywhere, Category = "Camera")
    UCameraComponent* FollowCamera;
};

Step 2: Implement in CPP

// MyThirdPersonCharacter.cpp
#include "MyThirdPersonCharacter.h"
#include "OmniWalkPro.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"

AMyThirdPersonCharacter::AMyThirdPersonCharacter()
{
    // OmniWalkPro is already added by parent class, but you can 
    // configure it here if needed
    if (UOmniWalkPro* OmniWalk = FindComponentByClass<UOmniWalkPro>())
    {
        OmniWalk->bDebugDismount = true;
        OmniWalk->TraceDistance = 300.0f;
    }
    
    // Setup standard 3rd person camera
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(GetRootComponent());
    CameraBoom->TargetArmLength = 300.0f;
    CameraBoom->bUsePawnControlRotation = true;
    
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    FollowCamera->SetupAttachment(CameraBoom);
    
    // OmniWalk handles rotation, so disable controller rotation
    bUseControllerRotationYaw = false;
}

void AMyThirdPersonCharacter::BeginPlay()
{
    Super::BeginPlay();  // Important: Calls interface implementations
}

void AMyThirdPersonCharacter::OnOmniWalkSurfaceAttached_Implementation(
    const FVector& SurfaceNormal)
{
    // Optional: Play landing effect, change animation, etc.
    UE_LOG(LogTemp, Log, TEXT("Attached to wall! Normal: %s"), 
        *SurfaceNormal.ToString());
}

void AMyThirdPersonCharacter::OnOmniWalkSurfaceDetached_Implementation()
{
    // Optional: Play jump effect, etc.
    UE_LOG(LogTemp, Log, TEXT("Detached from surface"));
}

What You Get Automatically

  • GetOmniWalkMovementComponent() → returns your CharacterMovementComponent
  • GetOmniWalkCapsuleComponent() → returns your CapsuleComponent
  • ✓ All location/rotation input methods implemented
  • ✓ Event callbacks (surface attached/detached, gravity changed)
  • ✓ OmniWalkPro component auto-initialized

Approach 2: Implement IOmniWalkable Interface

Use this approach when you have an existing character class you can't change the inheritance of, or when you're using a custom character base from another plugin or marketplace asset.

Advanced Best when you can't change your character's parent class

Step 1: Add Interface to Your Header

// MyExistingCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "IOmniWalkable.h"  // Add this include
#include "MyExistingCharacter.generated.h"

UCLASS()
class MYGAME_API AMyExistingCharacter : public ACharacter, public IOmniWalkable
{
    GENERATED_BODY()

public:
    AMyExistingCharacter();

    //~ Begin IOmniWalkable Interface
    virtual UCharacterMovementComponent* GetOmniWalkMovementComponent_Implementation() const override;
    virtual UCapsuleComponent* GetOmniWalkCapsuleComponent_Implementation() const override;
    virtual FVector GetOmniWalkLocation_Implementation() const override;
    virtual FVector GetOmniWalkUpVector_Implementation() const override;
    virtual FVector GetOmniWalkForwardVector_Implementation() const override;
    virtual FVector GetOmniWalkRightVector_Implementation() const override;
    virtual bool IsOmniWalkLocallyControlled_Implementation() const override;
    virtual FRotator GetOmniWalkControlRotation_Implementation() const override;
    virtual FVector ConsumeOmniWalkMovementInput_Implementation() override;
    virtual void AddOmniWalkMovementInput_Implementation(const FVector& WorldDirection, float ScaleValue) override;
    virtual void SetOmniWalkRotation_Implementation(const FQuat& NewRotation) override;
    virtual FQuat GetOmniWalkRotation_Implementation() const override;
    virtual void OmniWalkLaunch_Implementation(const FVector& LaunchVelocity, bool bXYOverride, bool bZOverride) override;
    virtual bool IsOmniWalkJumpPressed_Implementation() const override;
    virtual UInputComponent* GetOmniWalkInputComponent_Implementation() const override;
    virtual APlayerController* GetOmniWalkPlayerController_Implementation() const override;
    //~ End IOmniWalkable Interface

protected:
    UPROPERTY(VisibleAnywhere, Category = "OmniWalk")
    UOmniWalkPro* OmniWalkComponent;
    
    UPROPERTY(VisibleAnywhere, Category = "Camera")
    USpringArmComponent* CameraBoom;
    
    UPROPERTY(VisibleAnywhere, Category = "Camera")
    UCameraComponent* FollowCamera;
};

Step 2: Implement All Interface Methods

// MyExistingCharacter.cpp
#include "MyExistingCharacter.h"
#include "OmniWalkPro.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"

AMyExistingCharacter::AMyExistingCharacter()
{
    // CRITICAL: You must add the OmniWalkPro component yourself
    OmniWalkComponent = CreateDefaultSubobject<UOmniWalkPro>(TEXT("OmniWalkPro"));
    
    // Setup camera
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(GetRootComponent());
    CameraBoom->TargetArmLength = 300.0f;
    CameraBoom->bUsePawnControlRotation = true;
    
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    FollowCamera->SetupAttachment(CameraBoom);
    
    bUseControllerRotationYaw = false;
}

//~ Begin IOmniWalkable Implementation
UCharacterMovementComponent* AMyExistingCharacter::GetOmniWalkMovementComponent_Implementation() const
{
    return GetCharacterMovement();
}

UCapsuleComponent* AMyExistingCharacter::GetOmniWalkCapsuleComponent_Implementation() const
{
    return GetCapsuleComponent();
}

FVector AMyExistingCharacter::GetOmniWalkLocation_Implementation() const
{
    return GetActorLocation();
}

FVector AMyExistingCharacter::GetOmniWalkUpVector_Implementation() const
{
    return GetActorUpVector();
}

FVector AMyExistingCharacter::GetOmniWalkForwardVector_Implementation() const
{
    return GetActorForwardVector();
}

FVector AMyExistingCharacter::GetOmniWalkRightVector_Implementation() const
{
    return GetActorRightVector();
}

bool AMyExistingCharacter::IsOmniWalkLocallyControlled_Implementation() const
{
    return IsLocallyControlled();
}

FRotator AMyExistingCharacter::GetOmniWalkControlRotation_Implementation() const
{
    if (const APlayerController* PC = GetController<APlayerController>())
    {
        return PC->GetControlRotation();
    }
    return GetActorRotation();
}

FVector AMyExistingCharacter::ConsumeOmniWalkMovementInput_Implementation()
{
    return ConsumeMovementInputVector();
}

void AMyExistingCharacter::AddOmniWalkMovementInput_Implementation(
    const FVector& WorldDirection, float ScaleValue)
{
    AddMovementInput(WorldDirection, ScaleValue);
}

void AMyExistingCharacter::SetOmniWalkRotation_Implementation(const FQuat& NewRotation)
{
    SetActorRotation(NewRotation);
}

FQuat AMyExistingCharacter::GetOmniWalkRotation_Implementation() const
{
    return GetActorQuat();
}

void AMyExistingCharacter::OmniWalkLaunch_Implementation(
    const FVector& LaunchVelocity, bool bXYOverride, bool bZOverride)
{
    LaunchCharacter(LaunchVelocity, bXYOverride, bZOverride);
}

bool AMyExistingCharacter::IsOmniWalkJumpPressed_Implementation() const
{
    return bPressedJump;
}

UInputComponent* AMyExistingCharacter::GetOmniWalkInputComponent_Implementation() const
{
    return InputComponent;
}

APlayerController* AMyExistingCharacter::GetOmniWalkPlayerController_Implementation() const
{
    return GetController<APlayerController>();
}
//~ End IOmniWalkable Implementation

Approach Comparison

Aspect Approach 1: Inheritance Approach 2: Interface
Setup Complexity Minimal - just inherit and go Moderate - implement all methods
Code Required ~20 lines ~100 lines
Flexibility Limited to single inheritance Can implement multiple interfaces
Existing Characters Requires changing parent class Works with existing hierarchy
Best For New projects, new characters Established projects, marketplace assets
Wall Movement Reliability ✓ Fully tested & supported ⚠ May have edge cases
OmniWalk Component Auto-added by parent class Must add manually in constructor

Blueprint Setup (Both Approaches)

If you prefer Blueprint-only workflow, you can also use OmniWalk through Blueprint classes:

// Blueprint-Only Setup

  1. Create Blueprint → Parent Class: OmniWalkableCharacter
  2. Add ComponentOmniWalkPro (if not already present)
  3. Configure Settings in the OmniWalkPro component details:
    • Trace Distance: 250.0
    • Surface Trace Channel: WorldStatic
    • b Control Rotation: ✓ (checked)
    • b Use Internal Input Fix: ✗ (unchecked if using Blueprint input)
  4. Bind Events:
    • On Surface Attached → Play landing effects
    • On Surface Detached → Play jump effects

Next Steps

After setting up your character, complete the integration:

Common Setup Issues

  • Forgot to add OmniWalkPro component (Approach 2): Interface methods will compile but runtime will fail. Always add the component in your constructor.
  • Didn't call Super::BeginPlay(): Event callbacks won't fire. Always call parent BeginPlay.
  • Character doesn't stick to walls: Check that walkable surfaces have collision enabled on the trace channel (default: WorldStatic).
  • Camera rotation issues: Ensure you're using AOmniWalkPlayerController as your player controller class.

Character Setup Tiers

OmniWalk supports multiple integration paths, but they offer different levels of reliability and support. Choose the tier that matches your project's constraints.

Recommended

Tier 1

The Golden Path ⭐

AOmniWalkExampleCharacter

The complete, production-ready character. Everything is pre-configured and tested. Use this for new projects or when you can switch your character class.

  • Native IOmniWalkable implementation
  • Gravity-relative input via GetGravityRelativeDirection()
  • Wall movement fully tested & supported
  • Enhanced Input pre-configured
  • No adapter overhead

Best for:

New projects, prototyping, or when you can adopt the example character as your base.

Tier 2

Adapter Bridge

BP_ThirdPersonCharacter + Adapter

For existing Blueprint characters you can't easily convert. Uses the UOmniWalkCharacterAdapter to bridge non-interface characters.

  • Runtime adapter wrapper overhead
  • Input consumption may behave differently
  • Wall movement has known edge cases
  • No C++ changes required
  • Works with existing character assets

Best for:

Existing projects with complex Blueprint characters you need to retrofit quickly.

Tier 3

Custom Implementation

YourClass : IOmniWalkable

Implement the IOmniWalkable interface directly on your custom character class. Maximum flexibility, maximum responsibility.

  • Full control over all interface methods
  • Can extend from any character base
  • Must implement ~15 interface methods
  • You are responsible for correctness

Best for:

Advanced users with custom character bases from other plugins/assets.

Tier 1 Setup: AOmniWalkExampleCharacter

This is the path of least resistance

Use AOmniWalkExampleCharacter as your player character. It comes with everything pre-configured: Enhanced Input, gravity-relative movement, wall traversal, and proper IOmniWalkable implementation.

Option A: C++ Inheritance (Recommended)

// MyCharacter.h
#pragma once
#include "OmniWalkExampleCharacter.h"
#include "MyCharacter.generated.h"

UCLASS()
class MYGAME_API AMyCharacter : public AOmniWalkExampleCharacter
{
    GENERATED_BODY()
public:
    AMyCharacter();
    
protected:
    // Override callbacks for custom behavior
    virtual void OnOmniWalkSurfaceAttached_Implementation(
        const FVector& SurfaceNormal) override;
};

// MyCharacter.cpp
AMyCharacter::AMyCharacter()
{
    // Customize OmniWalk settings
    if (OmniWalkPro)
    {
        OmniWalkPro->bAutoEnableWallSlide = true;
        OmniWalkPro->WallSlideSpeed = 300.0f;
        OmniWalkPro->bDebugDismount = true; // For debugging
    }
    
    // Customize camera
    SpringArm->TargetArmLength = 350.0f;
    SpringArm->CameraRotationLagSpeed = 15.0f;
}

void AMyCharacter::OnOmniWalkSurfaceAttached_Implementation(
    const FVector& SurfaceNormal)
{
    Super::OnOmniWalkSurfaceAttached_Implementation(SurfaceNormal);
    
    // Add your custom logic here
    UE_LOG(LogTemp, Log, TEXT("Landed on surface: %s"), *SurfaceNormal.ToString());
}

Option B: Blueprint-Only (For Designers)

  1. Create Blueprint → Parent Class: OmniWalkExampleCharacter
  2. Set as Default Pawn in your Game Mode
  3. Customize Settings in the OmniWalkPro component details
  4. Bind Events like OnSurfaceAttached for FX
  5. Assign Animation Blueprint to the mesh

Input Flow Comparison

Understanding how input flows through each character type is crucial for proper setup. The two characters use fundamentally different input handling approaches.

Configuration Matrix

Character Type bUseInternalInputFix Input Method Wall Quality
ExampleCharacter false GetGravityRelativeDirection() ⭐⭐⭐ Excellent
BP_ThirdPersonChar false AddGravityRelativeMovement node ⭐⭐ Good
BP_ThirdPersonChar true Standard UE5 Input ⭐⭐ Good

Note: ExampleCharacter with bUseInternalInputFix = true causes double transformation - avoid this combination.

ExampleCharacter Input Flow

bUseInternalInputFix = false - External transformation via PlayerController
Input Action WASD / Stick HandleMove Local (X,Y) PlayerController GetGravityRelative Direction() World-space vector AddMovementInput Direct to CMC ✓ Bypasses OmniWalkPro input fix

Key Characteristics:

  • • Input is transformed before reaching the movement component
  • • Uses AOmniWalkPlayerController::GetGravityRelativeDirection() for camera-aligned wall movement
  • • OmniWalkPro's internal fix is completely bypassed
  • • Wall basis vectors are correctly aligned with camera view

BPThirdPersonChar Input Flow (Internal Fix Enabled)

bUseInternalInputFix = true - OmniWalkPro transforms input internally
Standard Input UE5 IMC Default AddMovementInput Raw camera-relative CharacterAdapter ConsumeInput() (reads stored input) FixMovementInput() Transform & re-apply CMC Movement ⚠ Adapter Overhead

Key Characteristics:

  • • Standard UE5 input flows normally into the CharacterMovementComponent
  • • OmniWalkPro's FixMovementInput() consumes the input via adapter
  • • Input is transformed and re-applied within the same tick
  • • Adds adapter overhead - input timing may differ slightly

BPThirdPersonChar Input Flow (External Node)

bUseInternalInputFix = false - Blueprint handles transformation
Input Action IA_Move Event Graph Your Blueprint Logic Add Gravity Relative Movement (Node) Transforms to world-space CMC Direct Input ✓ Bypasses internal fix • Same as ExampleCharacter approach

Key Characteristics:

  • • Similar to ExampleCharacter - transformation happens before CMC
  • • Uses OmniWalkBlueprintLibrary::AddGravityRelativeMovement() node
  • • No adapter overhead - input goes directly to CMC
  • • Requires modifying your Blueprint input handling

Critical: Avoid Double Transformation

❌ Wrong (Double Transform):

ExampleCharacter with bUseInternalInputFix = true
Input gets transformed twice → character moves slowly/stutters

✓ Correct:

ExampleCharacter: bUseInternalInputFix = false
BPThirdPersonChar: Either true OR use AddGravityRelativeMovement node

Tier 2 Setup: Adapter-Based (Existing Characters)

Known Limitations

The adapter pattern adds a layer of indirection. Input consumption, wall movement, and event timing may behave differently than with native IOmniWalkable implementations.

For BP_ThirdPersonCharacter (Auto-Injection)

  1. Tag Your Character

    Add the OmniWalk.AutoInject tag to your BP_ThirdPersonCharacter

  2. Configure OmniWalkPro Component

    When the component is auto-added, set these properties:

    • bUseInternalInputFix = false (CRITICAL)
    • bAutoEnableWallSlide = true
    • TraceDistance = 250.0
  3. Modify Input Handling

    Replace your movement input logic with:

    // In your movement input handling:
    Use the "Add Gravity Relative Movement" Blueprint node
    ForwardAxis = Input.Y (W/S)
    RightAxis = Input.X (A/D)
  4. Set Player Controller

    Use BP_OmniWalkPlayerController (or C++ AOmniWalkPlayerController)

What the Adapter Does

BP_ThirdPersonCharacter (plain ACharacter) wraps UOmniWalkCharacterAdapter (runtime wrapper) implements IOmniWalkable (interface)

The adapter bridges plain ACharacters to the OmniWalk system at runtime. This adds indirection and may cause timing/input issues.

Troubleshooting Adapter Issues

Issue Cause Solution
Wall movement unpredictable Input consumed differently via adapter Migrate to Tier 1 (AOmniWalkExampleCharacter)
Double input transformation bUseInternalInputFix = true with Blueprint input Set bUseInternalInputFix = false in component
Events not firing Adapter binding timing issue Check BeginPlay order; ensure component is valid
Wall slide not working CMC settings conflict with adapter Check AutoConfiguration settings on OmniWalkPro

Migration: Tier 2 → Tier 1

If you're experiencing issues with the adapter approach, consider migrating to AOmniWalkExampleCharacter. Here's the process:

  1. Create new Blueprint inheriting from OmniWalkExampleCharacter
  2. Transfer assets:
    • Skeletal Mesh
    • Animation Blueprint
    • Materials/Textures
    • Any additional components (sounds, FX, etc.)
  3. Migrate Blueprint logic:
    • Copy event graphs
    • Re-bind Input Actions (they use different names)
    • Transfer custom variables
  4. Update Game Mode to use new character class
  5. Test thoroughly - especially wall movement

Still Having Issues?

If wall movement or input feels "off" with the adapter, this is a known limitation. The adapter exists for quick prototyping, but AOmniWalkExampleCharacter is the production-ready solution. For new projects, start with Tier 1.

Auto Injection (No-Code)

OmniWalk can auto-inject the component at runtime. This is ideal for designers or rapid prototyping.

1. Tag

Add OmniWalk.Enabled to the Character's Tags.

2. Inject

Subsystem adds UOmniWalkPro at BeginPlay.

3. Align

Gravity aligns to surface normal automatically.

Important: Auto-injection does not swap your controller. For correct mouselook on walls and ceilings, use AOmniWalkPlayerController.

When to Avoid Auto Injection

In production codebases with custom Character subclasses, add the component explicitly so you can configure settings and input in one place.

Input Mapping

OmniWalk movement must be mapped onto the current gravity plane. The helper node handles this safely and consistently.

World vs Gravity Plane

Gravity Up World Forward Projected

Helper Node Flow

Axis Input Helper Node Add Movement

Preferred: Blueprint Helper Node

// Blueprint Input Flow

  1. InputAxis MoveForward → ForwardAxis
  2. InputAxis MoveRight → RightAxis
  3. Call OmniWalk Add Gravity-Relative Movement

Dismount Input

OmniWalk exposes a dedicated dismount path so Jump stays Jump on vertical surfaces. Bind Dismount and call OmniWalk Request Dismount (or let UOmniWalkPro auto-bind it).

Enhanced Input (Recommended)

  • Assign EnhancedDismountActionAsset (e.g., IA_Dismount).
  • Assign EnhancedDismountMappingAsset (e.g., IMC_OmniWalk).
  • OmniWalk adds the context and binds the action automatically.

Legacy Input

  • Define Dismount in DefaultInput.ini or Project Settings.
  • Call OmniWalk Request Dismount on the action.
  • Disable auto-binding via bAutoBindDismountInput if desired.

Manual Mapping (C++ or Blueprint)

Use the controller function directly if you want custom scaling or filtering:

// Map local input onto gravity plane
const FVector Local(ForwardAxis, RightAxis, 0.0f);
const FVector World = PC->GetGravityRelativeDirection(Local);
AddMovementInput(World, Scale);
Avoid Double Mapping

If you use the helper node or GetGravityRelativeDirection, leave bUseInternalInputFix disabled.

Strafe vs Follow

OmniWalk exposes a single, prominent facing mode: Strafe Mode (Face Camera).

Strafe Mode ON

Character always faces the camera direction. Great for third-person shooters and precise lateral movement.

Strafe Mode OFF

Character faces the direction of movement. Ideal for traversal-focused movement and platformers.

Faces Camera Faces Movement

This replaces legacy orientation toggles. Older assets auto-migrate at runtime.

Surface Adhesion

OmniWalk samples surface normals beneath and around the capsule, blends the result, and drives gravity direction and orientation. The system includes wall detection to anticipate transitions at corners.

Pipeline
1. Detect Floor + wall traces
2. Align Quat interpolation
3. Adhere Impulse for stickiness

Trace Layout

Surface Gravity Dir

Wall Transitions

When you approach a wall, OmniWalk detects a forward hit and begins transitioning the gravity normal so your character stays glued.

Corner stability: SurfaceLossGraceSeconds keeps the last surface normal briefly when detection drops out, preventing unintended falls at 90° transitions.

// Simplified concept
if (MoveIntoWall < -0.5f && WallVsUp < 0.5f)
{
    TargetSurfaceNormal = Hit.ImpactNormal;
}

Performance

Use SurfaceTraceInterval / StableSurfaceTraceInterval and bAdaptiveMultiPoint to reduce trace cost when movement is steady, while keeping full fidelity during active traversal.

Transition Smoothing

Large-angle turns can be smoothed using LargeAngleMinTransitionTime (minimum duration) and LargeAngleSpeedMultiplier (overall speed scale).

90° Pivot Timing

90° time Min Time Start Target

The curve visualizes a smooth 90° rotation over a guaranteed minimum duration, preventing snap transitions.

Wall Sliding (Optional)

Use SetWallSliding to enable a gentle downward slide on vertical surfaces. The slide accelerates using WallSlideAcceleration and can be capped with WallSlideSpeed.

// Toggle wall slide
UOmniWalkPro* OW = FindComponentByClass<UOmniWalkPro>();
if (OW)
{
    OW->SetWallSliding(true, 300.0f);
    OW->SetWallSlideAcceleration(1400.0f);
}

Auto-Enable Wall Sliding (v1.0.1+)

New in v1.0.1: Wall sliding can automatically enable when the character lands on a wall-like surface. The system detects horizontal input and intelligently skips sliding when the player is actively moving left/right.

// In your character constructor or BeginPlay
if (UOmniWalkPro* OmniWalkPro = FindComponentByClass<UOmniWalkPro>())
{
    // Auto-enable slide when on walls
    OmniWalkPro->bAutoEnableWallSlide = true;
    
    // Slide physics
    OmniWalkPro->WallSlideSpeed = 300.0f;        // Max slide speed (cm/s)
    OmniWalkPro->WallSlideAcceleration = 1500.0f; // Slide acceleration (cm/s²)
    
    // Input-aware resistance - pressing "up" stops the slide
    OmniWalkPro->bWallSlideResistsInput = true;
}

Input-Aware Behavior

When bWallSlideResistsInput = true, the slide behavior responds to player input:

Input Result
W (Up) Slide stops, move UP the wall
S (Down) Slide accelerates, move DOWN faster
A/D (Horizontal) Slide skipped entirely, move LEFT/RIGHT
No Input Auto-slide DOWN at configured speed

Debug Visualization

Enable bDebugDismount = true on the OmniWalkPro component to see yellow arrows showing the slide direction when wall sliding is active.

Gravity Volumes (Planets & Tubes)

While surface adhesion handles "walking on walls," Gravity Volumes solve the problem of jumping between them. When a character is airborne or transitions off a surface, OmniWalk checks for overlapping volumes to determine the "Down" direction.

Point Gravity

Gravity pulls toward the volume center. Perfect for planets, asteroids, and spherical worlds.

Directional Gravity

Gravity pulls in a fixed direction (local or world). Use for artificial gravity decks, elevators, or changing zones.

Spline Gravity

Gravity pulls toward the nearest point on a spline. Ideal for tubes, rings, and winding tunnels.

Setup

  1. Drag & Drop AOmniWalkGravityVolume into the level.
  2. Select Type (Point, Directional, Spline) in the Details panel.
  3. Adjust Bounds using the Box or Sphere component.
    • Point: Uses Sphere bounds.
    • Directional: Uses Box bounds.
    • Spline: Gravity pulls to the spline component (visualize with Debug).
  4. Set Priority if volumes overlap. Higher priority wins.
Note: Surface adhesion always takes precedence when grounded. Volumes only dictate gravity when you jump or fly.

Movement Mechanics

The example character includes three optional movement mechanics that work on any surface (floor, wall, ceiling) thanks to OmniWalk's gravity-relative system. All three are implemented in AOmniWalkExampleCharacter and inherited automatically by both FirstPerson and ThirdPerson subclasses.

Sprint

Hold Shift to move faster. Speed returns to normal when you stop, release Shift, or leave the ground.

Surface Slide

Crouch while sprinting to slide along the current surface. Capsule shrinks, velocity boosts, then decelerates to a stop.

Coyote Jump

Brief grace window to jump after walking off an edge. Works on any surface orientation.

Input Bindings

All mechanics use Enhanced Input actions created at runtime in SetupEnhancedInput() and added to the existing ExampleMappingContext.

Action Keyboard Gamepad Type
SprintAction Left Shift Left Thumbstick Click Boolean (hold)
CrouchAction Left Ctrl / C Face Button Bottom (A/X) Boolean (hold)

Sprint

Hold the sprint key to multiply MaxWalkSpeed by SprintSpeedMultiplier. Speed is only boosted while the character is grounded and actively providing movement input. Releasing the key, stopping, or leaving the surface restores the default speed.

Property Default Description
SprintSpeedMultiplier 1.65 Multiplier applied to MaxWalkSpeed while sprinting
// Sprint logic in Tick()
if (bIsSprinting && bGrounded && HasMovementInput)
{
    CMC->MaxWalkSpeed = DefaultMaxWalkSpeed * SprintSpeedMultiplier;
}
else
{
    CMC->MaxWalkSpeed = DefaultMaxWalkSpeed;
}

Surface Slide

Press crouch while sprinting and moving above SlideMinSpeed to initiate a slide. The capsule shrinks to SlideCapsuleHalfHeight, velocity is boosted by SlideInitialBoost, and engine friction is disabled. Braking is handled manually at SlideBrakingDeceleration cm/s² along the surface plane.

Sliding ends when speed drops below SlideMinSpeed, the character leaves the surface, crouch is released, or sprint starts. Capsule height restoration checks for overhead obstructions via a sweep trace before expanding.

Surface Slide Flow

Sprint + Crouch speed > min StartSlide() shrink + boost TickSlide() decelerate on surface EndSlide() restore capsule
Property Default Description
SlideInitialBoost 300.0 Speed added to current velocity at slide start (cm/s)
SlideBrakingDeceleration 500.0 Deceleration applied along the surface plane (cm/s²)
SlideMinSpeed 150.0 Slide ends when surface speed drops below this (cm/s)
SlideCooldown 0.5 Seconds between consecutive slides
SlideCapsuleHalfHeight 44.0 Capsule half-height during slide

Surface-Relative Braking

Slide deceleration operates on the velocity component projected onto the current gravity plane (via GetCurrentSurfaceNormal()). This means sliding works identically on floors, walls, and ceilings. On a downhill slope relative to gravity, the character naturally travels further; uphill, they decelerate faster.

// Surface-relative braking in TickSlide()
FVector SurfaceNormal = OmniWalkPro->GetCurrentSurfaceNormal();
FVector VelOnSurface = FVector::VectorPlaneProject(Velocity, SurfaceNormal);
float SpeedOnSurface = VelOnSurface.Size();

float NewSpeed = FMath::Max(SpeedOnSurface - SlideBrakingDeceleration * DeltaTime, 0.0f);
if (NewSpeed < SlideMinSpeed)
{
    EndSlide();
    return;
}

// Reconstruct velocity: keep normal component, scale surface component
FVector NormalComponent = Velocity - VelOnSurface;
CMC->Velocity = NormalComponent + VelOnSurface.GetSafeNormal() * NewSpeed;

Coyote Jump

When the character walks off an edge (detaches from a surface without jumping), a brief grace window allows jumping for CoyoteTimeSeconds after leaving the surface. This is implemented via CanJumpInternal_Implementation(), which returns true during the coyote window even though the character is airborne. The existing jump binding (JumpAction → ACharacter::Jump) works unchanged.

Property Default Description
CoyoteTimeSeconds 0.15 Grace window (seconds) to jump after walking off an edge

Coyote Jump Lifecycle

SurfaceDetached (not from jump) CoyoteTimer = 0.15s counting down... CanJumpInternal returns true Jump! timer reset
// Coyote jump via UE5's CanJumpInternal hook
bool AOmniWalkExampleCharacter::CanJumpInternal_Implementation() const
{
    if (CoyoteTimer > 0.0f)
    {
        return true;  // Allow jump even though airborne
    }
    return Super::CanJumpInternal_Implementation();
}

Customization

All three mechanics live in AOmniWalkExampleCharacter (the shared base class). Both AOmniWalkFirstPersonCharacter and AOmniWalkThirdPersonCharacter inherit them with zero additional code. All properties are exposed as EditAnywhere under the OmniWalk | Movement category and can be tuned per-instance in the editor.

// Override defaults in a subclass constructor
AMyCharacter::AMyCharacter()
{
    SprintSpeedMultiplier = 2.0f;     // Faster sprint
    SlideInitialBoost = 500.0f;       // Longer slides
    SlideBrakingDeceleration = 300.0f; // Slower deceleration
    CoyoteTimeSeconds = 0.2f;          // More forgiving window
}
Architecture note: These are example gameplay mechanics, not core gravity features. They do not modify UOmniWalkPro or any other OmniWalk system class. If your game needs different movement mechanics, use these implementations as a reference and replace them in your own character subclass.

Mag-Boots System

New in v1.1: A unified State System to switch between movement behaviors dynamically. Toggle between standard walking, high-adhesion magnetic locking, and low-friction hovering.

Standard

Default physics. Balanced adhesion and friction for normal gameplay.

Mag-Lock

Extreme adhesion (10,000), instant braking, no sliding. For hazardous vertical traversal.

Hover

Zero friction, momentum preservation. For skating or high-speed sliding.

Blueprint API

// Switch states at runtime
OmniWalkPro->SetMagBootState(EOmniWalkMagBootState::MagLock);
OmniWalkPro->SetMagBootState(EOmniWalkMagBootState::Hover);
OmniWalkPro->SetMagBootState(EOmniWalkMagBootState::Standard);
OmniWalkPro->SetMagBootState(EOmniWalkMagBootState::Off);

Spider Walk (Convex Traversal)

Characters can now seamlessly traverse 90-degree convex edges (Floor to Wall) without falling off.

Edge Wrapping Logic

When the floor trace fails, OmniWalk performs a secondary "Wrap Trace" over the ledge and back towards the wall. If a surface is found, the character rotates 90 degrees to attach to the new face.

Configuration
  • bEnableConvexTraversals (Default: True) - Master switch.
  • ConvexCheckDistance (Default: 50.0) - How far over the edge to check.

Airborne Gravity Projection

Enables seamless orbiting jumps around small planetoids or tubes.

When enabled, OmniWalk continuously projects gravity from the surface below the character while they are in the air. This prevents the "launch tangent" issue where jumping on a small sphere causes the character to fly off into space as their Up vector diverges from the curvature.

// Enable for planetoid gameplay
OmniWalkPro->bProjectGravityFromSurface = true;
OmniWalkPro->AirborneProjectionDistance = 2000.0f;

Key Settings

These values define the feel of your traversal. Start here before deeper tuning.

Property Purpose Typical Range
TraceDistance Floor trace length 120 - 350
WallDetectionDistance Forward wall scan 60 - 180
AlignmentSpeed Rotation interp speed 8 - 16
AdhesionForce Stick-to-surface impulse 1800 - 4500
bUseMultiPointAveraging Stabilize normals On for noisy meshes
MultiTraceOffset Multi-trace spacing 20 - 50
bUseInternalInputFix Optional input remap Off if using helper node
bKeepCameraUprightOnWalls Reduce camera roll on walls On / Off
WallUprightStrength Upright blend strength 0.0 - 1.0
bAllowJumpDismount Enable dismount requests On / Off
bAllowJumpHoldDismount Hold Jump to dismount On / Off
JumpHoldSeconds Hold time before dismount 0.2 - 0.6
WallDismountThreshold Wall check threshold 0.4 - 0.8
DismountImpulse Kick away from surface 300 - 900
DismountIgnoreSeconds Ignore surface after dismount 0.2 - 0.6
SurfaceTraceInterval Trace rate while moving 0.0 - 0.03
StableSurfaceTraceInterval Trace rate while stable 0.03 - 0.10
StableSpeedThreshold Stable speed cutoff 20 - 60
StableInputThreshold Stable input cutoff 0.1 - 0.2
bAdaptiveMultiPoint Skip extra traces when aligned On / Off
MultiPointNormalTolerance Normal similarity tolerance 0.1 - 0.3
LargeAngleMinTransitionTime Minimum 90° pivot duration 0.15 - 0.4
LargeAngleSpeedMultiplier Large-angle speed scale 0.4 - 0.8
WallSlideSpeed Wall slide max speed 150 - 400
WallSlideAcceleration Wall slide acceleration 800 - 1800
bAutoBindDismountInput Auto-bind Dismount action On / Off
EnhancedDismountActionAsset Enhanced Input action asset IA_Dismount
EnhancedDismountMappingAsset Enhanced Input mapping context IMC_OmniWalk
Strafe Mode (Face Camera) Facing behavior On = face camera
bEnableConvexTraversals Allow 90-degree edge wrapping On / Off
bProjectGravityFromSurface Orbiting jumps On / Off

Networking & Multiplayer

OmniWalk includes built-in network optimizations and security features for multiplayer games. Gravity state is replicated efficiently at 10Hz with client-side interpolation for smooth visuals.

RPC Rate Limiting

Prevents spam attacks with per-second call limits and cooldown validation.

Client Interpolation

100ms interpolation eliminates visual jitter from network updates.

Replication System

Gravity state is synced exclusively via the Client_SyncGravityState RPC at 10Hz (100ms intervals). This design minimizes network traffic while maintaining responsive gameplay.

Client-Side Interpolation

To prevent visual snapping when receiving network updates, the client smoothly interpolates from the current state to the replicated state over 100ms:

// In TickComponent - Client-side only
if (GetOwnerRole() < ROLE_Authority && ReplicationInterpAlpha < 1.0f)
{
    ReplicationInterpAlpha += DeltaTime * 10.0f; // 100ms interpolation
    
    CurrentSurfaceNormal = FMath::Lerp(
        CurrentSurfaceNormal, 
        LastReplicatedState.CurrentSurfaceNormal, 
        ReplicationInterpAlpha);
    CurrentSurfaceNormal.Normalize();
}

Listen Server Optimization

On listen servers, the local player's pawn skips replication since the client and server share the same memory. This reduces unnecessary network traffic.

Security Features

RPC Rate Limiting

Server RPCs are rate-limited to prevent spam attacks. The default limit is 30 calls per second with a minimum 33ms cooldown between calls:

bool UOmniWalkPro::ValidateRPCCall(float& LastCallTime, int32& CallCount, float DeltaTime)
{
    // Max 30 calls per second
    if (CallCount >= OmniWalkConstants::MaxRPCRatePerSecond) 
        return false;
    
    // 33ms cooldown between calls
    if (CurrentTime - LastCallTime < 0.033f) 
        return false;
    
    LastCallTime = CurrentTime;
    CallCount++;
    return true;
}

RPC Bounds Validation

Server RPCs validate all parameters to prevent physics exploits. For example, wall slide speed must be within reasonable bounds:

bool UOmniWalkPro::Server_SetWallSliding_Validate(bool bEnable, float SlideSpeed)
{
    // Validate slide speed within reasonable bounds
    if (SlideSpeed < 0.0f || SlideSpeed > OmniWalkConstants::MaxWallSlideSpeed)
    {
        UE_LOG(LogOmniWalk, Warning, TEXT("Invalid SlideSpeed [%f]"), SlideSpeed);
        return false;
    }
    return true;
}

Client RPC Safety

Client RPCs include comprehensive null and validity checks to prevent crashes if the owner becomes invalid during execution:

void UOmniWalkPro::Client_SyncGravityState_Implementation(
    const FOmniWalkReplicationState& State)
{
    // Validate state data before applying
    if (!State.CurrentSurfaceNormal.IsNormalized())
    {
        UE_LOG(LogOmniWalk, Warning, TEXT("Invalid surface normals"));
        return;
    }
    
    // Safety checks for owner validity
    if (!GetOwner() || !IsValid(GetOwner()))
    {
        return; // Owner became invalid, skip update
    }
}

Constants Reference

All magic numbers used in the networking system are available in the OmniWalkConstants namespace:

namespace OmniWalkConstants
{
    constexpr float DefaultReplicationInterval = 0.1f;  // 10Hz
    constexpr float MaxWallSlideSpeed = 5000.0f;        // cm/s
    constexpr float MaxWallSlideAcceleration = 50000.0f; // cm/s²
    constexpr float MaxRPCRatePerSecond = 30.0f;        // calls/sec
}

Network Architecture Overview

Server Authority 10Hz updates RPC Client Interpolation 100ms smoothing Security Rate limiting Bounds check Null validation

Local Control Solver

AOmniWalkPlayerController builds a stable, gravity-relative view quaternion and keeps yaw/pitch consistent when the gravity up vector changes. The camera manager then applies this rotation directly to the view.

Stable Reference

The controller maintains a forward reference that rotates with gravity, preventing camera flips.

Local Yaw/Pitch

Mouse deltas are applied around the gravity up axis instead of world Z.

Wall Upright Assist

When gravity aligns to near-vertical walls, the controller can blend the view "Up" toward world up to keep movement intuitive. Use bKeepCameraUprightOnWalls and WallUprightStrength to tune how much roll is reduced.

Gravity Up Forward

View rotation is built from a gravity-aligned basis using quaternions.

// Conceptual flow (simplified)
FQuat ViewQuat = BuildViewQuaternion();    // gravity-relative
SetControlRotation(ViewQuat.Rotator());    // camera follows this rotation

Example Level

OmniWalk ships with an automated example level and an editor utility to generate it.

Menu Entries
  • Window → OmniWalk → Create OmniWalk Example Level
  • Window → OmniWalk → Create Example Level and Open
  • Window → OmniWalk → Remove OmniWalk Example Assets
  • Window → OmniWalk → Rebuild Example Level and Open

The utility auto-creates /Game/OmniWalkExamples/BP_OW_CreateExampleLevel and spawns AOmniWalkExampleCharacter + AOmniWalkDemoGenerator.

Level Layout (Top-Down)

Spawn Sphere
/Game/OmniWalkExamples/
├─ OW_Example
└─ BP_OW_CreateExampleLevel

Debugger

The OmniWalk editor module adds a telemetry tab to visualize alignment strength, gravity vectors, and trace data.

Open it via Window → OmniWalk Debugger.

Alignment Strength

Dot product of actor up vs surface normal. 1.0 means perfectly flush.

Gravity Vector

Real-time X/Y/Z values to diagnose jitter zones and transition issues.

Telemetry Snapshot

Alignment Gravity Vec Trace Dist Normal

Extension Modules

These optional components extend OmniWalk beyond its locomotion core. Each module is independently attachable — add only what your project needs.

Trigger Zones & Spline Volume

Priority-resolved gravity override volumes. Fixed, attract, repulse, and spline-linked modes. Targets all, tagged, or specific actors.

Physics Field

Propagates the character's gravity to nearby physics-simulating objects. Crates, pickups, and debris fall the right "down".

Surface Response

Maps Unreal Physical Materials to adhesion, friction, and speed presets via a Data Asset. Drives footstep sound routing via OnSurfaceTypeChanged.

Cinematic Control & AI Patrol

Freeze surface detection and animate gravity for cutscenes. Drive AI pawns along spline-defined patrol routes with loop, ping-pong, and once modes.

Open Extension Modules Documentation

Troubleshooting

Common issues and solutions. Check here first if you're experiencing unexpected behavior.

Wall Movement Issues

Input Double-Transformation

Symptom: Character moves slowly or stutters when walking on walls.

Cause: Both the internal input fix and Blueprint input transformation are active simultaneously.

Fix: Set bUseInternalInputFix = false in the OmniWalkPro component details:

// In your character constructor or component settings
OmniWalkPro->bUseInternalInputFix = false;  // When using AddGravityRelativeMovement

Wall Movement Wrong Directions

Symptom: W/S keys move horizontally and A/D keys move vertically on walls.

Cause: Wall basis vectors were incorrectly constructed (cross product order).

Fix: This is fixed in v1.0.1+. Ensure you're using the latest version:

// Correct wall basis construction (v1.0.1+)
FVector WallUp = FVector::VectorPlaneProject(FVector::UpVector, GravityUp);
FVector WallRight = FVector::CrossProduct(WallUp, GravityUp);  // Correct order!

Wall Slide Not Working

Symptom: Character doesn't slide down vertical surfaces.

Cause: Wall sliding may not be enabled, or the surface isn't detected as wall-like.

Fix: Enable auto-wall-slide and check detection:

OmniWalkPro->bAutoEnableWallSlide = true;
OmniWalkPro->WallSlideSpeed = 300.0f;
OmniWalkPro->bWallSlideResistsInput = true;

Network & Multiplayer Issues

Client RPC Crashes

Symptom: Crash when gravity changes in multiplayer.

Cause: Owner became invalid during RPC execution (race condition).

Fix: Fixed in v1.0.1+ with comprehensive null checks. Ensure you have the latest version.

Jittery Movement in Multiplayer

Symptom: Character snaps or jitters when gravity updates.

Cause: No interpolation between network updates.

Fix: This is handled automatically in v1.0.1+ with 100ms client-side interpolation.

Input Issues

UI / Menu Interaction Broken

Symptom: Mouse cursor disappears or UI is unresponsive on BeginPlay.

Cause: Default "Game Only" input mode enforcement.

Fix: Disable the auto-input mode in the Player Controller:

// In AOmniWalkPlayerController defaults
bAutoSetGameInputMode = false;

Enhanced Input Not Working

Symptom: Look input doesn't respond with Enhanced Input.

Fix: Ensure you're using AOmniWalkPlayerController and the look mapping context is registered:

// In your GameMode or Level Blueprint
PlayerControllerClass = AOmniWalkPlayerController::StaticClass();

Double Input with Adapter-Based Character

Symptom: Character moves twice as fast or input feels "heavy".

Cause: Auto-injected components previously defaulted to bUseInternalInputFix = true.

Fix: Fixed in v1.0.1+. Auto-injected components now default to false.

Debug Tools

Enable debug visualization to diagnose issues:

// In your character BeginPlay or constructor
if (UOmniWalkPro* OW = FindComponentByClass<UOmniWalkPro>())
{
    OW->bDebugDismount = true;  // Shows surface detection, slide direction, etc.
}

Debug visualization is wrapped in WITH_EDITOR and only available in editor builds.

Still Having Issues?

If you're still experiencing issues after trying these solutions, check the Character Setup Tiers section to ensure you're using the recommended integration path. The adapter-based approach (Tier 2) has known limitations that may cause edge-case behavior.

Support & Legal

About

Budapest, HU

GregOrigin

Created by Andras Gregori @ Gregorigin, a single dad currently based outside Budapest, HU.

"Building tools that empower creators to shape worlds."

Disclaimers

OmniWalk ships as a C++ plugin for Unreal Engine 5.4+ and requires a C++ compile step on first install in a Blueprint-only project.

Always test your surfaces with representative collision meshes. Highly irregular normals can produce jitter without multi-point averaging.

Because setup varies across projects, engines, and input stacks, integration can take iteration; if you hit snags, reach out before considering a refund—most issues are solvable with a quick config pass.