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
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.
Quick Start
This is the shortest reliable path to working wall-walk movement. It assumes a standard UE Character.
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.
- Character has
UOmniWalkProcomponent. - Controller is
AOmniWalkPlayerController. - Input mapped using helper node or controller function.
bUseInternalInputFixdisabled 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.
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
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: 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.
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.
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
- Create Blueprint → Parent Class:
OmniWalkableCharacter - Add Component →
OmniWalkPro(if not already present) - Configure Settings in the OmniWalkPro component details:
Trace Distance: 250.0Surface Trace Channel: WorldStaticb Control Rotation: ✓ (checked)b Use Internal Input Fix: ✗ (unchecked if using Blueprint input)
- Bind Events:
On Surface Attached→ Play landing effectsOn Surface Detached→ Play jump effects
Next Steps
After setting up your character, complete the integration:
Map Input
Configure gravity-relative movement input
Tune Settings
Adjust trace distances, adhesion, and transitions
Troubleshoot
Common issues and solutions
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
AOmniWalkPlayerControlleras 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.
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
IOmniWalkableimplementation -
✓
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)
- Create Blueprint → Parent Class:
OmniWalkExampleCharacter - Set as Default Pawn in your Game Mode
- Customize Settings in the OmniWalkPro component details
- Bind Events like
OnSurfaceAttachedfor FX - 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
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)
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)
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)
-
Tag Your Character
Add the
OmniWalk.AutoInjecttag to your BP_ThirdPersonCharacter -
Configure OmniWalkPro Component
When the component is auto-added, set these properties:
- bUseInternalInputFix = false (CRITICAL)
- bAutoEnableWallSlide = true
- TraceDistance = 250.0
-
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) -
Set Player Controller
Use
BP_OmniWalkPlayerController(or C++AOmniWalkPlayerController)
What the Adapter Does
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:
- Create new Blueprint inheriting from
OmniWalkExampleCharacter - Transfer assets:
- Skeletal Mesh
- Animation Blueprint
- Materials/Textures
- Any additional components (sounds, FX, etc.)
- Migrate Blueprint logic:
- Copy event graphs
- Re-bind Input Actions (they use different names)
- Transfer custom variables
- Update Game Mode to use new character class
- 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.
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
Helper Node Flow
Preferred: Blueprint Helper Node
// Blueprint Input Flow
- InputAxis MoveForward → ForwardAxis
- InputAxis MoveRight → RightAxis
- 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
DismountinDefaultInput.inior Project Settings. - Call
OmniWalk Request Dismounton the action. - Disable auto-binding via
bAutoBindDismountInputif 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);
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.
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.
Trace Layout
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
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
- Drag & Drop
AOmniWalkGravityVolumeinto the level. - Select Type (Point, Directional, Spline) in the Details panel.
- 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).
- Set Priority if volumes overlap. Higher priority wins.
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
| 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
// 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
}
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.
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
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.
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.
Window → OmniWalk → Create OmniWalk Example LevelWindow → OmniWalk → Create Example Level and OpenWindow → OmniWalk → Remove OmniWalk Example AssetsWindow → OmniWalk → Rebuild Example Level and Open
The utility auto-creates /Game/OmniWalkExamples/BP_OW_CreateExampleLevel and spawns
AOmniWalkExampleCharacter + AOmniWalkDemoGenerator.
Level Layout (Top-Down)
/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
Extension Modules
These optional components extend OmniWalk beyond its locomotion core. Each module is independently attachable — add only what your project needs.
Priority-resolved gravity override volumes. Fixed, attract, repulse, and spline-linked modes. Targets all, tagged, or specific actors.
Propagates the character's gravity to nearby physics-simulating objects. Crates, pickups, and debris fall the right "down".
Maps Unreal Physical Materials to adhesion, friction, and speed presets via a Data Asset. Drives footstep sound routing via OnSurfaceTypeChanged.
Freeze surface detection and animate gravity for cutscenes. Drive AI pawns along spline-defined patrol routes with loop, ping-pong, and once modes.
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
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.