NWH Wheel Controller 3D
Search Results for

    Show / Hide Table of Contents

    Wheel Controller Manager

    WheelControllerManager coordinates every enabled WheelController and runs wheel physics in substeps. Game code supplies control values; the manager owns simulation timing.

    The runtime types on this page are in the NWH.WheelController3D namespace.

    Lifecycle

    WheelControllerManager.Instance finds the existing manager or creates a DontDestroyOnLoad GameObject named WheelControllerManager. A standalone scene does not need a manually created manager.

    An enabled wheel registers with the manager under its targetRigidbody. Wheels that target the same Rigidbody share one WheelControllerGroup. When the last wheel leaves a group, the manager removes that group.

    The relevant execution orders are:

    Component Order Role
    WheelController 100 Register and prepare wheels
    WheelControllerManager 110 Run the grouped substep loop

    Substep rate

    targetEffectiveRate is the global target rate in hertz and defaults to 200. For each Rigidbody group, the manager calculates:

    target count = ceil(targetEffectiveRate * Time.fixedDeltaTime - 0.001)
    safety count = ceil(Time.fixedDeltaTime / 0.05)
    substeps = clamp(max(target count, safety count), 1, 8)
    substep delta time = Time.fixedDeltaTime / substeps
    

    For example, a 200 Hz target at Unity's 50 Hz fixed rate produces four wheel substeps. At 60 Hz it also produces four, for an effective wheel rate of 240 Hz. The ceiling keeps the effective rate at or above the requested value except when the eight-substep cap is reached; the small tolerance prevents floating-point noise from adding a substep at an exact integer ratio. The safety count also prevents a wheel substep from exceeding 0.05 seconds when the project's fixed timestep is larger than that.

    Set WheelControllerGroup.targetEffectiveRateOverride to a positive value for a per-Rigidbody target. Zero or a negative value uses the manager's global rate.

    using NWH.WheelController3D;
    using UnityEngine;
    
    public sealed class WheelRateOverride : MonoBehaviour
    {
        [SerializeField] private Rigidbody targetRigidbody;
        [SerializeField] private float targetRate = 200f;
    
        private void Start()
        {
            WheelControllerGroup group =
                WheelControllerManager.Instance.GetGroup(targetRigidbody);
    
            if (group != null)
            {
                group.targetEffectiveRateOverride = targetRate;
            }
        }
    }
    

    Call GetGroup() after the wheels have enabled and registered. It returns null when the Rigidbody has no registered wheels.

    What happens in a physics tick

    For each non-sleeping group, the manager:

    1. reads the Rigidbody into the group's cached WheelControllerState;
    2. chooses that group's substep count;
    3. begins each substep and checks early wake conditions;
    4. invokes the optional ISubstepCallback;
    5. calls the internal wheel substep for every wheel not already stepped by that callback;
    6. coordinates static-friction break and anti-roll-bar forces;
    7. integrates the cached state; and
    8. writes the final velocity, wheel transforms, and contacted-body forces.

    Application code should not call an internal wheel step method. Assign motorTorque, brakeTorque, and steerAngle before the manager's execution point and let the manager advance the wheels.

    Optional substep callback

    WheelControllerGroup has one substepCallback. The current interface signature is:

    void OnBeforeSubstep(float substepDt, WheelControllerGroup group);
    

    NWH Vehicle Physics 2 uses this callback for its powertrain. Do not replace that callback on a Vehicle Physics vehicle. A standalone advanced integration may implement ISubstepCallback when it must calculate torque at the same rate as the wheels:

    using NWH.WheelController3D;
    using UnityEngine;
    
    public sealed class StandaloneSubstepCallback : MonoBehaviour, ISubstepCallback
    {
        [SerializeField] private Rigidbody targetRigidbody;
        private WheelControllerGroup _group;
    
        private void Start()
        {
            _group = WheelControllerManager.Instance.GetGroup(targetRigidbody);
            Attach();
        }
    
        private void OnEnable()
        {
            // OnEnable runs before Start the first time, so _group can still be null.
            Attach();
        }
    
        private void OnDisable()
        {
            Detach();
        }
    
        private void OnDestroy()
        {
            Detach();
        }
    
        private void Attach()
        {
            if (_group != null)
            {
                _group.substepCallback = this;
            }
        }
    
        private void Detach()
        {
            if (_group != null && _group.substepCallback == this)
            {
                _group.substepCallback = null;
            }
        }
    
        public void OnBeforeSubstep(
            float substepDt,
            WheelControllerGroup group)
        {
            // Calculate and assign wheel torque for this group here.
        }
    }
    

    Public manager API

    Member Purpose
    Instance Find or create the singleton
    targetEffectiveRate Global target wheel rate in hertz
    substepCount Substep count calculated for the most recently processed group
    Register(WheelController) Register a wheel under its target Rigidbody
    Deregister(WheelController) Remove a wheel from its current group
    GetGroup(Rigidbody) Return the group for a Rigidbody, or null
    BreakAllStaticFriction(Rigidbody, bool) Break static friction for that group's grounded wheels
    ResetWheelState(Rigidbody) Clear spring travel, wheel spin, load and contact state on that group
    CalculateSubstepCount(float, float) Static; the substep count for a target rate and fixed timestep

    Call ResetWheelState() after teleporting or respawning a vehicle. Without it the wheels keep the suspension pose, spin and contact state they had before the jump, which usually shows up as a vehicle that lurches or spins its wheels for a moment at the new position.

    The public static timing fields are physicsFrameCount, currentSubstep, substepDeltaTime, FixedDeltaTime, Gravity, and GravityMagnitude. Because different groups may use different rates, substepCount, currentSubstep, and substepDeltaTime describe the group currently being processed, or the last one processed.

    Standalone controls

    The Base Sample's CarController writes the current API directly:

    • _Wheel.power selects wheels that receive motorTorque;
    • _Wheel.steer selects wheels that receive steerAngle; and
    • _Wheel.handbrake selects wheels that receive handbrake brakeTorque.

    It does not call a wheel simulation method. See Car Controller and Input.

    Troubleshooting

    GetGroup() returns null

    Confirm that at least one enabled Wheel Controller targets that Rigidbody, then query after wheel OnEnable() has run.

    More substeps are not being used

    The manager clamps the calculated count to 1 through 8. Confirm Time.fixedDeltaTime, the global targetEffectiveRate, and any positive group override.

    A sleeping vehicle does not consume wheel time

    This is expected. A group whose Rigidbody can remain asleep is skipped until wheel torque or another wake condition requires processing.

    In this article
    Back to top Copyright © NWH - Vehicle Physics, Aerodynamics, Dynamic Water Physics