v0.3.0

OmniWalk Changelog

Comprehensive overhaul with interface-based architecture, event-driven design, and multiplayer support

πŸ“Š

Release Overview

Summary of changes in version 0.3.0

18+
Critical Fixes
9
Architectural Improvements
15+
New Features
6
Performance Optimizations
5
UE 5.7 Compatibility Fixes

This is a comprehensive presentation of OmniWalk's 0.3 update. It's an overhaul that transforms the plugin from a character-specific component into a flexible, interface-driven system. This release introduces multiplayer support, event-driven architecture, and extensive configuration options while maintaining full backward compatibility. We also resolved 33 issues across 15 source files, including delegate type mismatches, replication conflicts, PIE session safety, and full UE 5.7 compatibility.

πŸ›

Critical Fixes

Resolved stability and safety issues

Initialization Race Condition

Subsystem now defers injection by 0.1s to ensure all actors have completed BeginPlay. Added validation for HasBegunPlay() before component initialization.

Dangerous Raw Pointer Caching

Converted BoundInputComponent from raw pointer to TWeakObjectPtr<UInputComponent> to prevent dangling pointer crashes during possession changes.

Input Double-Binding Risk

Added static flags to prevent repeated failed asset loads and bAlreadyBound checks to prevent duplicate input bindings.

Missing Null Checks

Comprehensive null pointer validation added in HandleLookInput, BuildViewQuaternion, and DetectSurface to prevent crashes.

GEditor Nullptr Risk

SOmniWalkDebugger now validates GEditor, WorldContext, World, PlayerController, and Pawn before access to prevent crashes in standalone builds.

Division by Zero

Added SizeSquared() > 0.01f checks before all vector normalization operations to prevent divide-by-zero crashes.

Delegate Type Mismatch

Changed OnPreGravityChange and OnPostGravityChange from single-cast to DECLARE_DYNAMIC_MULTICAST_DELEGATE. Single-cast delegates are incompatible with BlueprintAssignable, causing silent binding failures in Blueprints.

Dual-Path Replication Conflict

Removed conflicting DOREPLIFETIME registrations that fought with the Client_SyncGravityState RPC path. Gravity state now replicates exclusively via throttled 10Hz Client RPC to avoid desync.

CrossProduct Handedness Error

Fixed FVector::CrossProduct(UpVector, WorldForward) to CrossProduct(WorldForward, UpVector) in movement input fix. Incorrect operand order produced inverted right-vector, causing mirrored strafing on non-standard gravity surfaces.

Client-Side Authority Violation

Non-authority clients in RequestDismount and SetWallSliding now return after sending the Server RPC instead of also setting state locally. Prevents replication desync where client state diverges from server truth.

Stale Dismount Trigger

Moved bDismountRequested = false to unconditional clear after the dismount check block. Previously, the flag could persist across ticks and fire unexpectedly when the character next touched a surface.

Double Surface-Detach Event

Added bWasGrounded = false after StartDismount to prevent the grounded-state transition from firing NotifySurfaceDetached a second time in the same tick.

Zero-Vector Gravity Crash

Added early-exit guard in SetGravityDirection when the input vector cannot be normalized (zero or near-zero), preventing NaN propagation through the movement component.

Wall Normal Overriding Floor Data

Wall detection now only uses wall impact normal when HitCount == 0 (no floor hits). Previously, wall hits could override valid floor data during surface transitions, causing gravity flicker.

Camera Pitch Limits Blocking Gravity Transitions

Changed ViewPitchMin/Max, ViewYawMin/Max, and ViewRollMin/Max to -360/360 in AOmniWalkPlayerCameraManager. Default UE limits clamped the camera during gravity rotations, causing sudden snaps.

Gamepad Look Inversion

Added UInputModifierNegate to Gamepad_RightY mapping in AOmniWalkPlayerController. Without it, gamepad vertical look was inverted relative to mouse input.

Look Action Double-Bind on Re-Possession

Stored LookAction = ExistingLookAction after binding to existing IA_Look. The top-of-function guard checked LookAction but it was never set on the existing-action path, causing duplicate bindings on each possession.

πŸ—οΈ

Architectural Improvements

Foundation-level redesign for flexibility and maintainability

πŸ”Œ

IOmniWalkable Interface

Decouples OmniWalk from ACharacter dependency, enabling support for any pawn type through a clean contract.

  • Works with custom APawn classes
  • Easy to mock for unit testing
  • Clear API contract
  • Backward compatible
πŸ“‘

Event-Driven Architecture

Replaces polling with event callbacks for surface detection and gravity changes.

  • Immediate state change notification
  • No polling overhead
  • Blueprint-assignable events
  • Better separation of concerns
πŸ—ΊοΈ

Spatial Prioritization

Intelligent initialization that prioritizes nearby actors and defers distant ones to prevent frame spikes.

  • No level-start frame spikes
  • Scales to large open worlds
  • Configurable thresholds
  • Randomized deferred delays
βš™οΈ

Configurable Settings

UDeveloperSettings-based configuration accessible in Project Settings without code changes.

  • Project-specific defaults
  • Version-control friendly
  • Per-platform overrides
  • Editor UI integration
IOmniWalkable.h C++
UINTERFACE(MinimalAPI, Blueprintable)
class UOmniWalkable : public UInterface
{
    GENERATED_BODY()
};

class IOmniWalkable
{
    GENERATED_BODY()

public:
    // Core movement interface
    virtual UCharacterMovementComponent* 
        GetOmniWalkMovementComponent() const;
    
    // Event callbacks
    virtual void OnOmniWalkSurfaceAttached(const FVector& Normal);
    virtual void OnOmniWalkSurfaceDetached();
    virtual void OnOmniWalkGravityChanged(const FVector& New, const FVector& Old);
};
🌐

Multiplayer Support

Full network replication with client prediction

Feature Description Type
State Replication FOmniWalkReplicationState with quantized vectors NEW
Server RPCs Validated remote procedure calls for dismount/wallslide NEW
Client Sync Throttled 10Hz gravity state synchronization NEW
Authority Checks Prevents client-side cheating NEW
Bandwidth Optimization FVector_NetQuantize compression NEW
Network Replication C++
// Server RPC with validation
UFUNCTION(Server, Reliable, WithValidation)
void Server_RequestDismount();

bool Server_RequestDismount_Validate()
{
    return true; // Add game-specific validation
}

// Efficient state replication
USTRUCT()
struct FOmniWalkReplicationState
{
    FVector_NetQuantize CurrentSurfaceNormal;  // Compressed
    FVector_NetQuantize TargetSurfaceNormal;
    uint8 bIsGrounded : 1;
    uint8 bWallSlideEnabled : 1;
};
⚑

Performance Optimizations

Smarter processing to maintain high frame rates

πŸ“

Distance-Based Culling

Skip processing for actors beyond configurable distance from controller.

MaxDistanceFromController = 5000.0f;
🎯

Adaptive Sampling

Skip multi-point traces when surface hasn't changed significantly.

bAdaptiveMultiPoint = true;
⏱️

Trace Throttling

Different trace intervals for moving vs stable actors.

StableSurfaceTraceInterval = 0.05f;
πŸ’Ύ

State Caching

Cache surface state to avoid redundant calculations between ticks.

bHasCachedSurface, CachedSurfaceNormal

Performance Comparison

Metric Before (v0.2) After (v0.3) Improvement
Level Start Frame Time ~50ms spike ~5ms smooth 90% better
Trace Calls (stable) 5 per tick 1 per 20 ticks 99% reduction
Far Actor Processing Always Skipped Configurable
Bandwidth Usage N/A (no network) ~20 bytes/sec New Feature
πŸ”§

Configuration System

Project-specific tuning without code changes

DefaultOmniWalk.ini INI
; OmniWalk Plugin Configuration

[/Script/OmniWalk.OmniWalkSettings]
; Default trace distance for surface detection (cm)
DefaultTraceDistance=250.0

; Default collision channel for traces
DefaultSurfaceTraceChannel=WorldStatic

; Default alignment speed for gravity transitions
DefaultAlignmentSpeed=12.0

; Enable async traces by default (experimental)
bDefaultUseAsyncTraces=False

; Whether to enable debug logging by default
bDefaultDebugDismount=False

[/Script/OmniWalk.OmniWalkSubsystem]
; Maximum actors to initialize immediately per frame
MaxImmediateInit=10

; Distance beyond which actors are deferred (cm)
DeferDistanceCm=10000.0

; Enable spatial prioritization
bUseSpatialPrioritization=True

Opt-Out Tags

Control OmniWalk behavior per-actor using gameplay tags:

Tag Applied To Effect
OmniWalk.Disabled Actor Completely disables OmniWalk
OmniWalk.Ignore Actor / Component Same as Disabled / Skip SpringArm config
OmniWalk.NoAutoInject Actor Prevents subsystem auto-injection
OmniWalk.Enabled Actor Required for subsystem auto-injection
πŸ›‘οΈ

Stability & Safety

PIE session isolation, input lifecycle, and delegate architecture hardening

πŸ”„

PIE Session Isolation

All static local variables replaced with instance members across OmniWalkPro and OmniWalkPlayerController.

  • No stale state leaking between PIE sessions
  • LastGravityConflictWarningTime now per-instance
  • bAttemptedAssetLoad, bFoundExistingAction now per-controller
  • bWarnedDoubleMapping, bWarnedMissingEnhancedAssets marked Transient
🧹

Input Binding Lifecycle

Proper cleanup and guard logic for Enhanced Input bindings to prevent dangling delegates and duplicate registrations.

  • EndPlay calls ClearBindingsForObject(this)
  • EnsureDismountInputBinding early-exits when already bound
  • NewObject guards prevent name collision assertion on re-spawn
  • UpdateRotation clears accumulated input when no pawn
πŸ“£

Delegate Architecture Rework

Gravity change callbacks restructured into separate observation and veto paths for cleaner Blueprint integration.

  • Multicast OnPreGravityChange for observation
  • Single-cast OnPreGravityChangeVeto for blocking
  • Veto only blocks gravity set, not full tick logic
  • All surface events use multicast delegates
⏲️

Subsystem Timer Safety

Timer handles stored as members and properly cancelled during teardown. Settings integration reads from UOmniWalkSettings.

  • FTimerHandle members cleared in Deinitialize()
  • Removed manual BeginPlay() calls (RegisterComponent handles it)
  • Settings-driven MaxImmediateInit and DeferDistanceCm
  • Eliminated identical #if ENGINE_MAJOR_VERSION branches

Additional Hardening

File Change Type
OmniWalkStyle.cpp Null checks for StyleInstance and FindPlugin in Shutdown FIXED
SOmniWalkDebugger.cpp Changed IsActive() to IsGrounded() for adhesion status display FIXED
OmniWalkPlayerController.cpp InitializeViewFromPawn cascading fallbacks for degenerate gravity planes IMPROVED
OmniWalkDemoGenerator.cpp Component lifecycle: SetRootComponent before RegisterComponent, added AddInstanceComponent FIXED
OmniWalkBlueprintLibrary.cpp Removed unused include OmniWalkPlayerController.h IMPROVED
OmniWalk.uplugin Fixed CreatedByURL to use https:// scheme FIXED
πŸ”§

UE 5.7 Compatibility

API changes and stricter compiler behavior in Unreal Engine 5.7

FObjectInitializer Constructor Chain

AOmniWalkExampleCharacter now accepts const FObjectInitializer& and forwards it to AOmniWalkableCharacter. UE 5.7 enforces that child classes of FObjectInitializer-only parents cannot use default constructors.

GetActionBindings() API Removed

UEnhancedInputComponent::GetActionBindings() does not exist in UE 5.7. Removed the duplicate-binding iteration check in SetupEnhancedLookInput; the LookAction guard at the top of the function already prevents double-binding.

FInputActionValue::IsNonNull() API Removed

FInputActionValue::IsNonNull() does not exist in UE 5.7. Removed the validation check in HandleLookInput; Get<FVector2D>() safely returns zero for empty values.

TScriptInterface Implicit Conversion

UE 5.7 no longer allows implicit conversion from TScriptInterface<IOmniWalkable> to IOmniWalkable*. Added explicit .GetInterface() calls at all 7 call sites in TickComponent where the wrapper is passed to private methods.

DeveloperSettings Module Dependency

Added DeveloperSettings to PublicDependencyModuleNames in OmniWalk.Build.cs. UOmniWalkSettings inherits from UDeveloperSettings, which moved to its own module in UE 5.7 and requires an explicit link.

OmniWalk.Build.cs C#
PublicDependencyModuleNames.AddRange(
    new string[]
    {
        "Core",
        "CoreUObject",
        "Engine",
        "InputCore",
        "EnhancedInput",
        "PhysicsCore",
        "DeveloperSettings"   // Required for UOmniWalkSettings (UE 5.7+)
    }
);

Removed Unused Dependencies

Module Reason for Removal
GameplayTags Plugin uses FName-based actor tags, not the GameplayTags system
OnlineSubsystem No online service integration; replication uses built-in UE networking
πŸ“š

API Reference

Key classes and interfaces

New Classes

Class Type Description
IOmniWalkable Interface Core interface for OmniWalk-compatible actors
AOmniWalkableCharacter Class Base character class implementing IOmniWalkable
UOmniWalkSettings Settings Project-wide configuration settings
FOmniWalkReplicationState Struct Compressed network replication state
FOmniWalkSurfaceResult Struct Async surface detection result

New UOmniWalkPro Properties

Property Type Description
bControlRotation bool Allow OmniWalk to control character rotation
MaxDistanceFromController float Maximum distance for processing (0 = unlimited)
bUseAsyncTraces bool Use async surface detection (experimental)
OnSurfaceAttached Delegate Event fired when surface adhesion begins
OnSurfaceDetached Delegate Event fired when surface adhesion ends
OnPreGravityChange Multicast Delegate Event fired before gravity change (observation only)
OnPreGravityChangeVeto Delegate Single-cast veto delegate that can block a gravity change
OnPostGravityChange Delegate Event fired after gravity change