// ╔════════════════════════════════════════════════════════════════╗ // ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║ // ║ Licensed under Unity Asset Store Terms of Service: ║ // ║ https://unity.com/legal/as-terms ║ // ║ Use permitted only in compliance with the License. ║ // ║ Distributed "AS IS", without warranty of any kind. ║ // ╚════════════════════════════════════════════════════════════════╝ #region using UnityEngine; #endregion namespace NWH.Common.Utility { /// /// Proportional-Integral-Derivative controller for smooth value regulation. /// Used for automated control systems like cruise control, stability systems, and steering assistance. /// /// /// PID controllers combine three control strategies: /// - Proportional: Reacts to current error /// - Integral: Eliminates accumulated error over time /// - Derivative: Anticipates future error based on rate of change /// Tune the three gain values to achieve desired response characteristics. /// public class PIDController { /// /// Maximum output value. Output will be clamped to this value. /// public float maxValue; /// /// Minimum output value. Output will be clamped to this value. /// public float minValue; private float _processVariable; /// /// Creates a new PID controller with specified gains and output limits. /// /// Proportional gain (Kp). Higher values increase response to current error. /// Integral gain (Ki). Higher values eliminate steady-state error faster. /// Derivative gain (Kd). Higher values dampen oscillations. /// Minimum output value. /// Maximum output value. public PIDController(float gainProportional, float gainIntegral, float gainDerivative, float outputMin, float outputMax) { GainDerivative = gainDerivative; GainIntegral = gainIntegral; GainProportional = gainProportional; maxValue = outputMax; minValue = outputMin; } /// /// The derivative term is proportional to the rate of /// change of the error /// public float GainDerivative { get; set; } /// /// The integral term is proportional to both the magnitude /// of the error and the duration of the error /// public float GainIntegral { get; set; } /// /// The proportional term produces an output value that /// is proportional to the current error value /// /// /// Tuning theory and industrial practice indicate that the /// proportional term should contribute the bulk of the output change. /// public float GainProportional { get; set; } /// /// Adjustment made by considering the accumulated error over time /// /// /// An alternative formulation of the integral action, is the /// proportional-summation-difference used in discrete-time systems /// public float IntegralTerm { get; private set; } /// /// The current value /// public float ProcessVariable { get { return _processVariable; } set { ProcessVariableLast = _processVariable; _processVariable = value; } } /// /// The last reported value (used to calculate the rate of change) /// public float ProcessVariableLast { get; private set; } /// /// The desired value /// public float SetPoint { get; set; } = 0; /// /// The controller output /// /// /// timespan of the elapsed time /// since the previous time that ControlVariable was called /// /// Value of the variable that needs to be controlled public float ControlVariable(float timeSinceLastUpdate) { // Guard against zero or very small deltaTime to prevent NaN/Infinity // This can happen during game pause or time manipulation const float EPSILON = 0.0001f; if (timeSinceLastUpdate < EPSILON) { // Return proportional-only control when time is too small return Mathf.Clamp(GainProportional * (SetPoint - ProcessVariable), minValue, maxValue); } float error = SetPoint - ProcessVariable; // integral term calculation IntegralTerm += GainIntegral * error * timeSinceLastUpdate; IntegralTerm = Mathf.Clamp(IntegralTerm, minValue, maxValue); // derivative term calculation float dInput = _processVariable - ProcessVariableLast; float derivativeTerm = GainDerivative * (dInput / timeSinceLastUpdate); // proportional term calculation float proportionalTerm = GainProportional * error; float output = proportionalTerm + IntegralTerm - derivativeTerm; output = Mathf.Clamp(output, minValue, maxValue); return output; } } }