NWH Wheel Controller 3D
Search Results for

    Show / Hide Table of Contents

    WheelControllerManager

    WheelControllerManager is a singleton that runs physics substepping for all WheelController components. It lets wheel physics run at higher frequencies than Unity's native FixedUpdate.


    Overview

    Purpose

    Unity's physics typically runs at 50Hz (0.02s Fixed Timestep). For accurate wheel physics simulation, higher frequencies are beneficial:

    • Better friction calculations at high speeds
    • Smoother suspension response over rough terrain
    • More accurate tire grip during rapid weight transfers
    • Stable vehicle dynamics during aggressive driving

    WheelControllerManager solves this by running multiple physics substeps within each FixedUpdate.

    Key Classes

    Class Purpose
    WheelControllerManager Singleton manager that runs the substep loop
    WheelControllerGroup Groups wheels per Rigidbody, handles force accumulation
    WheelControllerState Caches Rigidbody state for substep integration

    Architecture

    WheelControllerManager (singleton, ExecutionOrder 110)
    ├── Dictionary<int, WheelControllerGroup> - Groups indexed by Rigidbody ID
    │
    └── FixedUpdate()
        │
        ├── READ: group.BeginFrame()
        │   └── state.ReadFromRigidbody()
        │
        ├── SUBSTEP LOOP (N iterations)
        │   ├── group.BeginSubstep()
        │   │   └── Reset force accumulators
        │   │
        │   ├── ISubstepCallback.OnBeforeSubstep(dt)
        │   │   └── Powertrain integration (if registered)
        │   │
        │   ├── wheel.SubStep(dt, state, group)
        │   │   ├── Ground detection
        │   │   ├── Calculate suspension forces
        │   │   ├── Calculate friction forces
        │   │   └── Accumulate to group
        │   │
        │   ├── group.CoordinateStaticFrictionBreak()
        │   │
        │   └── group.EndSubstep()
        │       └── Apply forces to state, integrate velocity
        │
        └── WRITE: group.EndFrame()
            └── rb.linearVelocity = state.velocity
    

    Configuration

    Target Effective Rate

    The manager automatically calculates substep count based on Unity's fixedDeltaTime:

    // Example: Target 200Hz with 50Hz physics
    float fixedDeltaTime = 0.02f;  // 50Hz
    int targetRate = 200;          // Hz
    int substepCount = Mathf.RoundToInt(targetRate * fixedDeltaTime);
    // Result: 4 substeps per FixedUpdate
    

    Default Settings

    Setting Default Description
    Target Effective Rate 200 Hz Target physics rate
    Substep Count 4 At 50Hz base physics
    Min Substeps 1 Minimum substep count
    Max Substeps 8 Maximum substep count

    Performance Impact

    Substeps Effective Rate Relative Cost
    1 50 Hz 1.0x (baseline)
    2 100 Hz ~1.8x
    4 200 Hz ~3.5x
    8 400 Hz ~7.0x

    Per-Vehicle Configuration

    Each vehicle can override the global substep count through WheelControllerGroup:

    // Get the wheel group for a vehicle
    WheelControllerGroup group = WheelControllerManager.Instance.GetGroup(vehicleRigidbody);
    
    // Override substep count for this vehicle
    group.targetEffectiveRateOverride = 400; // Higher rate for player vehicle
    
    // Or use lower rate for AI vehicles
    aiGroup.targetEffectiveRateOverride = 100;
    

    Use Cases

    • Player vehicles: Higher substep rate for best handling feel
    • AI vehicles: Lower rate to save performance
    • Distant vehicles: Minimal rate or sleep entirely
    • Physics-heavy scenarios: Temporarily increase rate

    Powertrain Integration

    VehicleController registers with WheelControllerGroup to run powertrain calculations at each substep.

    ISubstepCallback Interface

    public interface ISubstepCallback
    {
        void OnBeforeSubstep(float substepDt);
    }
    

    Registration

    // In VehicleController.Start() after initialization:
    _rigidbodyGroup = WheelControllerManager.Instance?.GetGroup(vehicleRigidbody);
    if (_rigidbodyGroup != null)
    {
        _rigidbodyGroup.substepCallback = this;
    }
    

    Callback Implementation

    // In VehicleController:
    public void OnBeforeSubstep(float substepDt)
    {
        // Powertrain calculates torques at substep frequency
        powertrain.engine.IntegrateDownwards(substepDt);
    }
    

    Benefits

    1. Accurate torque calculation: Powertrain responds to wheel changes at each substep
    2. No double updates: Single code path for wheel physics
    3. Proper counter-torque: Available immediately in same substep
    4. Clean separation: Manager orchestrates, VehicleController provides callbacks

    Static Properties

    Access global physics state:

    // Global physics tick counter (increments each FixedUpdate)
    int frameCount = WheelControllerManager.PhysicsFrameCount;
    
    // Current substep index (0 to substepCount-1)
    int substep = WheelControllerManager.CurrentSubstep;
    
    // Delta time for current substep
    float substepDt = WheelControllerManager.SubstepDeltaTime;
    

    API Reference

    Singleton Access

    // Get manager instance (auto-creates if needed)
    WheelControllerManager manager = WheelControllerManager.Instance;
    

    Wheel Registration

    Wheels register automatically on enable:

    // Called automatically by WheelController.OnEnable()
    WheelControllerManager.Instance.Register(wheelController);
    
    // Called automatically by WheelController.OnDisable()
    WheelControllerManager.Instance.Deregister(wheelController);
    

    Group Access

    // Get the wheel group for a Rigidbody
    WheelControllerGroup group = WheelControllerManager.Instance.GetGroup(rigidbody);
    
    // Check if group exists
    if (group != null)
    {
        // Access group properties
        float effectiveRate = group.effectiveRate;
        int wheelCount = group.wheelCount;
    }
    

    Static Friction Control

    // Break static friction on all wheels of a vehicle
    WheelControllerManager.Instance.BreakAllStaticFriction(rigidbody);
    

    Events

    WheelControllerGroup Events

    // Fired when any wheel in the group breaks static friction
    WheelControllerGroup group = WheelControllerManager.Instance.GetGroup(rb);
    group.OnStaticFrictionBroke += OnStaticFrictionBroke;
    
    private void OnStaticFrictionBroke()
    {
        // Handle static friction break (e.g., propagate to trailer)
    }
    

    Ground Detection in Substeps

    Why Sweeps Run Every Substep

    Ground detection must run at each substep, not just once per frame:

    1. Ground is static: Cannot extrapolate hit points with vehicle movement
    2. Position changes: Wheel position changes during integration
    3. Accuracy required: Suspension calculation needs real intersection data

    Substep Optimization

    For performance, multicast ground detection uses adaptive resolution:

    Substep 0: Full multicast (3-7 sphere casts) - accurate detection
    Substeps 1-N: Single biased cast at cached angle - uses substep 0 result
    

    Performance impact:

    • Before optimization: 4 wheels x 4 substeps x 5 casts = 80 casts/frame
    • After optimization: (4 x 5) + (4 x 1 x 3) = 32 casts/frame (~60% reduction)

    Integration with VehicleController

    Execution Order

    Order Component Action
    90 VehicleController.FixedUpdate Input, steering, brakes, wheel groups
    100 WheelController.OnEnable Register with manager
    110 WheelControllerManager.FixedUpdate Substep loop with callbacks

    Data Flow

    VehicleController.FixedUpdate (Order 90)
    ├── Process input
    ├── Calculate steering angles
    ├── Calculate brake torques
    └── Update wheel groups (NOT powertrain)
    
    WheelControllerManager.FixedUpdate (Order 110)
    ├── For each substep:
    │   ├── Call VehicleController.OnBeforeSubstep()
    │   │   └── Powertrain calculates torques
    │   └── WheelController.SubStep()
    │       └── Uses torques from powertrain
    └── Apply final forces to Rigidbody
    

    Standalone Usage

    WheelController can be used without VehicleController (standalone mode):

    // Wheels still register with WheelControllerManager
    // Substepping works normally
    // No powertrain callback (wheels use external motor/brake torque)
    
    // Apply torque directly
    wheelController.motorTorque = 500f;
    wheelController.brakeTorque = 1000f;
    

    The CarController demo script shows standalone usage:

    // Simple car controller without full VehiclePhysics
    foreach (var wheel in wheels)
    {
        if (wheel.powered)
            wheel.wheelController.motorTorque = throttle * maxTorque;
        if (wheel.handbrake)
            wheel.wheelController.brakeTorque = handbrake * maxBrake;
    }
    

    Performance Considerations

    Substep Count Guidelines

    Platform Recommended Notes
    Desktop (Player) 4-8 substeps Best handling feel
    Desktop (AI) 2-4 substeps Good balance
    Mobile (Player) 2-4 substeps Performance priority
    Mobile (AI) 1-2 substeps Minimal overhead

    Optimization Strategies

    1. LOD-based substeps: Reduce substep count for distant vehicles
    2. Sleep inactive vehicles: Disable WheelController when not needed
    3. Ground detection mode: Use simpler cast for flat terrain
    4. Pool vehicles: Reuse wheel groups when spawning/despawning

    Profiling

    Key markers to watch in Unity Profiler:

    • WheelControllerManager.FixedUpdate
    • WheelController.SubStep
    • StandardGroundDetection.WheelCast
    • StandardFriction.UpdateFriction

    Common Issues

    Wheels Not Substepping

    Symptom: Wheels update once per frame instead of multiple substeps

    Causes:

    • WheelController not enabled
    • No Rigidbody on parent
    • WheelControllerManager destroyed

    Solution: Verify wheel is enabled and has valid Rigidbody reference

    Bouncy Suspension

    Symptom: Vehicle bounces excessively on terrain

    Causes:

    • Matrix caching not updated per substep
    • Incorrect suspension settings

    Solution: Ensure ground detection updates cached matrix each substep

    Performance Issues

    Symptom: High CPU usage in wheel physics

    Solutions:

    • Reduce substep count
    • Use simpler ground detection for AI
    • Enable LOD-based substep reduction
    • Profile to identify bottlenecks

    Technical Notes

    Singleton Lifecycle

    • Auto-creates on first access
    • Uses DontDestroyOnLoad to persist across scenes
    • Returns null during application quit to prevent teardown issues

    Thread Safety

    • All wheel physics runs on main thread
    • Manager coordinates wheel groups synchronously
    • No multi-threading in substep loop

    Compatibility

    • Works with Unity 6000+
    • Compatible with all render pipelines
    • Supports both Input System and Input Manager

    Related Documentation

    • WheelController - Individual wheel physics
    • FrictionPreset - Tire friction configuration
    • NWH Vehicle Physics 2 Documentation - Architecture and Powertrain integration
    • Edit this page
    In this article
    Back to top Copyright © NWH - Vehicle Physics, Aerodynamics, Dynamic Water Physics